summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/animation/AnimationHandler.java3
-rw-r--r--core/java/android/app/Notification.java4
-rw-r--r--core/java/android/app/WallpaperManager.java15
-rw-r--r--core/java/android/app/servertransaction/PendingTransactionActions.java44
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java35
-rw-r--r--core/java/android/hardware/camera2/CameraExtensionCharacteristics.java3
-rw-r--r--core/java/android/os/PowerManagerInternal.java12
-rw-r--r--core/java/android/service/dreams/DreamActivity.java18
-rw-r--r--core/java/android/service/dreams/DreamService.java14
-rw-r--r--core/java/android/service/wallpaper/IWallpaperService.aidl2
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java3
-rw-r--r--core/java/android/view/RemoteAnimationTarget.java12
-rw-r--r--core/java/android/window/BackProgressAnimator.java136
-rw-r--r--core/java/android/window/IOnBackInvokedCallback.aidl13
-rw-r--r--core/java/android/window/OnBackAnimationCallback.java10
-rw-r--r--core/java/android/window/OnBackInvokedCallback.java28
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java13
-rw-r--r--core/java/com/android/internal/app/AppLocaleCollector.java139
-rw-r--r--core/java/com/android/internal/app/LocalePickerWithRegion.java161
-rw-r--r--core/java/com/android/internal/app/ResolverListAdapter.java15
-rw-r--r--core/java/com/android/internal/app/SuggestedLocaleAdapter.java10
-rw-r--r--core/java/com/android/internal/app/SystemLocaleCollector.java66
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetService.aidl1
-rw-r--r--core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java815
-rw-r--r--core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java64
-rw-r--r--core/java/com/android/internal/dynamicanimation/animation/Force.java27
-rw-r--r--core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java314
-rw-r--r--core/java/com/android/internal/dynamicanimation/animation/SpringForce.java323
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java8
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java16
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl3
-rw-r--r--core/res/AndroidManifest.xml29
-rw-r--r--core/res/res/anim/dream_activity_close_exit.xml2
-rw-r--r--core/res/res/anim/dream_activity_open_enter.xml2
-rw-r--r--core/res/res/anim/dream_activity_open_exit.xml2
-rw-r--r--core/res/res/layout/notification_template_header.xml1
-rw-r--r--core/res/res/values-am/strings.xml14
-rw-r--r--core/res/res/values-ar/strings.xml4
-rw-r--r--core/res/res/values-as/strings.xml178
-rw-r--r--core/res/res/values-ca/strings.xml12
-rw-r--r--core/res/res/values-es-rUS/strings.xml4
-rw-r--r--core/res/res/values-es/strings.xml4
-rw-r--r--core/res/res/values-eu/strings.xml10
-rw-r--r--core/res/res/values-fr-rCA/strings.xml4
-rw-r--r--core/res/res/values-fr/strings.xml2
-rw-r--r--core/res/res/values-hi/strings.xml2
-rw-r--r--core/res/res/values-hr/strings.xml10
-rw-r--r--core/res/res/values-ko/strings.xml2
-rw-r--r--core/res/res/values-lv/strings.xml2
-rw-r--r--core/res/res/values-nb/strings.xml32
-rw-r--r--core/res/res/values-pt-rBR/strings.xml4
-rw-r--r--core/res/res/values-pt-rPT/strings.xml8
-rw-r--r--core/res/res/values-pt/strings.xml4
-rw-r--r--core/res/res/values-ru/strings.xml4
-rw-r--r--core/res/res/values-te/strings.xml16
-rw-r--r--core/res/res/values-tl/strings.xml2
-rw-r--r--core/res/res/values-zh-rCN/strings.xml2
-rw-r--r--core/res/res/values-zh-rTW/strings.xml4
-rw-r--r--core/res/res/values/config.xml19
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java565
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java18
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java111
-rw-r--r--core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java71
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java201
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java15
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java204
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml4
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_caption_title.xml1
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml4
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml6
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml14
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-gu/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml6
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java146
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java119
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java77
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt79
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java121
-rw-r--r--libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java136
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt72
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java45
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java30
-rw-r--r--packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml2
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java6
-rw-r--r--packages/PackageInstaller/res/values-as/strings.xml34
-rw-r--r--packages/SettingsLib/res/values-am/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-as/strings.xml6
-rw-r--r--packages/SettingsLib/res/values-fr/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-te/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java40
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java9
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java10
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java20
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java75
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java38
-rw-r--r--packages/SoundPicker/res/values-am/strings.xml2
-rw-r--r--packages/SystemUI/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt138
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt14
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt5
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt6
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt107
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt168
-rw-r--r--packages/SystemUI/checks/Android.bp4
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt6
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt100
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt1
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt149
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt5
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt22
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt208
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt48
-rw-r--r--packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt45
-rw-r--r--packages/SystemUI/ktfmt_includes.txt62
-rw-r--r--packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt13
-rw-r--r--packages/SystemUI/plugin/Android.bp1
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt16
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt)77
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogLevel.kt)11
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogMessage.kt)25
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt)41
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt)18
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt)54
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt (renamed from packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt)6
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt (renamed from packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt)30
-rw-r--r--packages/SystemUI/plugin/tests/log/LogBufferTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt)54
-rw-r--r--packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml20
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml1
-rw-r--r--packages/SystemUI/res-keyguard/values-af/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-am/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ar/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-as/strings.xml21
-rw-r--r--packages/SystemUI/res-keyguard/values-az/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-be/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-bg/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-bn/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-bs/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ca/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-cs/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-da/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-de/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-el/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rAU/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rCA/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rGB/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rIN/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rXC/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-es-rUS/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-es/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-et/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-eu/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-fa/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-fi/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-fr/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-gl/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-gu/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-hi/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-hr/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-hu/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-hy/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-in/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-is/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-it/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-iw/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-ja/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-ka/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-kk/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-km/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-kn/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ko/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ky/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-lo/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-lt/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-lv/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-mk/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-ml/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-mn/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-mr/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ms/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-my/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-nb/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ne/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-nl/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-or/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-pa/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-pl/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-pt/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-ro/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ru/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-si/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-sk/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-sl/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-sq/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-sr/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-sv/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-sw/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ta/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-te/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-th/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-tl/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-tr/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-uk/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ur/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-uz/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-vi/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-zu/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values/config.xml5
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml1
-rw-r--r--packages/SystemUI/res-keyguard/values/strings.xml6
-rw-r--r--packages/SystemUI/res/drawable-hdpi/textfield_default_filled.9.pngbin0 -> 5959 bytes
-rw-r--r--packages/SystemUI/res/drawable-mdpi/textfield_default_filled.9.pngbin0 -> 5927 bytes
-rw-r--r--packages/SystemUI/res/drawable-xhdpi/textfield_default_filled.9.pngbin0 -> 5781 bytes
-rw-r--r--packages/SystemUI/res/drawable-xxhdpi/textfield_default_filled.9.pngbin0 -> 5843 bytes
-rw-r--r--packages/SystemUI/res/drawable/edit_text_filled.xml36
-rw-r--r--packages/SystemUI/res/drawable/media_squiggly_progress.xml (renamed from packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml)2
-rw-r--r--packages/SystemUI/res/drawable/qs_media_background.xml2
-rw-r--r--packages/SystemUI/res/drawable/qs_media_light_source.xml2
-rw-r--r--packages/SystemUI/res/layout-land/auth_credential_password_view.xml96
-rw-r--r--packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml102
-rw-r--r--packages/SystemUI/res/layout/auth_credential_password_view.xml105
-rw-r--r--packages/SystemUI/res/layout/auth_credential_pattern_view.xml119
-rw-r--r--packages/SystemUI/res/layout/chipbar.xml (renamed from packages/SystemUI/res/layout/media_ttt_chip.xml)16
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay.xml4
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay_legacy.xml160
-rw-r--r--packages/SystemUI/res/layout/combined_qs_header.xml4
-rw-r--r--packages/SystemUI/res/layout/media_carousel.xml4
-rw-r--r--packages/SystemUI/res/layout/status_bar_expanded.xml66
-rw-r--r--packages/SystemUI/res/values-am/strings.xml54
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml36
-rw-r--r--packages/SystemUI/res/values-as/strings.xml44
-rw-r--r--packages/SystemUI/res/values-bg/strings.xml36
-rw-r--r--packages/SystemUI/res/values-bn/strings.xml36
-rw-r--r--packages/SystemUI/res/values-ca/strings.xml36
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml36
-rw-r--r--packages/SystemUI/res/values-da/strings.xml36
-rw-r--r--packages/SystemUI/res/values-de/strings.xml36
-rw-r--r--packages/SystemUI/res/values-es/strings.xml36
-rw-r--r--packages/SystemUI/res/values-et/strings.xml36
-rw-r--r--packages/SystemUI/res/values-eu/strings.xml36
-rw-r--r--packages/SystemUI/res/values-fi/strings.xml36
-rw-r--r--packages/SystemUI/res/values-fr/strings.xml36
-rw-r--r--packages/SystemUI/res/values-gl/strings.xml36
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml36
-rw-r--r--packages/SystemUI/res/values-hy/strings.xml36
-rw-r--r--packages/SystemUI/res/values-in/strings.xml36
-rw-r--r--packages/SystemUI/res/values-is/strings.xml36
-rw-r--r--packages/SystemUI/res/values-kk/strings.xml36
-rw-r--r--packages/SystemUI/res/values-kn/strings.xml36
-rw-r--r--packages/SystemUI/res/values-ko/strings.xml36
-rw-r--r--packages/SystemUI/res/values-land/styles.xml38
-rw-r--r--packages/SystemUI/res/values-lv/strings.xml36
-rw-r--r--packages/SystemUI/res/values-ml/strings.xml36
-rw-r--r--packages/SystemUI/res/values-mr/strings.xml36
-rw-r--r--packages/SystemUI/res/values-my/strings.xml36
-rw-r--r--packages/SystemUI/res/values-nb/strings.xml36
-rw-r--r--packages/SystemUI/res/values-ne/strings.xml36
-rw-r--r--packages/SystemUI/res/values-or/strings.xml36
-rw-r--r--packages/SystemUI/res/values-pa/strings.xml36
-rw-r--r--packages/SystemUI/res/values-pl/strings.xml36
-rw-r--r--packages/SystemUI/res/values-pt-rPT/strings.xml36
-rw-r--r--packages/SystemUI/res/values-ro/strings.xml36
-rw-r--r--packages/SystemUI/res/values-ru/strings.xml36
-rw-r--r--packages/SystemUI/res/values-si/strings.xml36
-rw-r--r--packages/SystemUI/res/values-sq/strings.xml36
-rw-r--r--packages/SystemUI/res/values-sv/strings.xml36
-rw-r--r--packages/SystemUI/res/values-sw600dp-land/styles.xml47
-rw-r--r--packages/SystemUI/res/values-sw600dp-port/styles.xml44
-rw-r--r--packages/SystemUI/res/values-sw720dp-land/styles.xml48
-rw-r--r--packages/SystemUI/res/values-sw720dp-port/styles.xml44
-rw-r--r--packages/SystemUI/res/values-ta/strings.xml36
-rw-r--r--packages/SystemUI/res/values-tr/strings.xml36
-rw-r--r--packages/SystemUI/res/values-uk/strings.xml36
-rw-r--r--packages/SystemUI/res/values-ur/strings.xml36
-rw-r--r--packages/SystemUI/res/values-zh-rCN/strings.xml36
-rw-r--r--packages/SystemUI/res/values-zh-rHK/strings.xml10
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml2
-rw-r--r--packages/SystemUI/res/values/attrs.xml13
-rw-r--r--packages/SystemUI/res/values/bools.xml7
-rw-r--r--packages/SystemUI/res/values/dimens.xml6
-rw-r--r--packages/SystemUI/res/values/integers.xml7
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/res/values/styles.xml81
-rw-r--r--packages/SystemUI/res/xml/combined_qs_header_scene.xml51
-rw-r--r--packages/SystemUI/res/xml/qqs_header.xml11
-rw-r--r--packages/SystemUI/res/xml/qs_header_new.xml16
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt10
-rw-r--r--packages/SystemUI/shared/Android.bp4
-rw-r--r--packages/SystemUI/shared/proguard.flags4
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt256
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt11
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt21
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java45
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt36
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java15
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java8
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java24
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java239
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java25
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java380
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java108
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java302
-rw-r--r--packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt54
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt102
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java17
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java40
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java (renamed from packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java)9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java45
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt16
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dumpable.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt326
-rw-r--r--packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java110
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java729
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java963
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java (renamed from packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java)12
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java482
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java174
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeService.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java91
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/sysui.proto27
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java348
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt351
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt169
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt98
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt209
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt1143
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt162
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt (renamed from packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt)12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaData.kt)124
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt)65
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt (renamed from packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt)89
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt)232
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt (renamed from packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt)114
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt (renamed from packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt)49
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt (renamed from packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt)20
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt (renamed from packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt)12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt)31
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt)152
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt)765
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt)211
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt)85
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt)89
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt112
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaBrowserFactory.java (renamed from packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt)148
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java (renamed from packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java (renamed from packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt)71
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt (renamed from packages/SystemUI/src/com/android/systemui/media/AnimationBindHandler.kt)26
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt223
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt (renamed from packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt)104
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt (renamed from packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt)81
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt (renamed from packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt)165
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt1327
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt)72
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt)382
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java (renamed from packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java)26
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt)1019
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaHost.kt)214
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt)89
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt)96
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt)454
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt)52
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt (renamed from packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt)89
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java (renamed from packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java (renamed from packages/SystemUI/src/com/android/systemui/media/MediaDataUtils.java)12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFeatureFlag.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt)198
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/SmallHash.java (renamed from packages/SystemUI/src/com/android/systemui/media/SmallHash.java)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt143
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt247
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt202
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto47
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt159
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl27
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl21
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl7
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java251
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt284
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java119
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt200
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt212
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt293
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt173
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java215
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java165
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java195
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java140
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java95
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt208
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt (renamed from packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt101
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/condition/Condition.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto26
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java17
-rw-r--r--packages/SystemUI/tests/Android.bp50
-rw-r--r--packages/SystemUI/tests/Android.mk93
-rw-r--r--packages/SystemUI/tests/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/tests/res/layout/custom_view_dark.xml1
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt78
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt159
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java60
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt274
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt135
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java132
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java115
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java189
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java93
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java103
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java97
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java152
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt259
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt426
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaViewHolderTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt)26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt)270
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt)63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java)5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt)141
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt)892
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt)64
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt)105
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt)184
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt)185
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt)65
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/AnimationBindHandlerTest.kt)13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt)23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt)48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt645
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt)667
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt)108
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt)164
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt)16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt)11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt)23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt868
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt691
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt104
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt103
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt107
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java195
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java241
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt210
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt76
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt)70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt148
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt107
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt116
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt352
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt64
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt131
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java24
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java27
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java19
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java25
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java4
-rw-r--r--services/core/java/com/android/server/display/BrightnessTracker.java10
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java96
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java21
-rw-r--r--services/core/java/com/android/server/display/DisplayModeDirector.java122
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java9
-rw-r--r--services/core/java/com/android/server/display/PersistentDataStore.java35
-rw-r--r--services/core/java/com/android/server/dreams/DreamController.java196
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java8
-rw-r--r--services/core/java/com/android/server/dreams/DreamShellCommand.java8
-rw-r--r--services/core/java/com/android/server/media/MediaButtonReceiverHolder.java7
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java19
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java21
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java2
-rw-r--r--services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java25
-rw-r--r--services/core/java/com/android/server/power/PowerGroup.java4
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java90
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java19
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStepConductor.java13
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java4
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java3
-rw-r--r--services/core/java/com/android/server/wm/SafeActivityOptions.java17
-rw-r--r--services/core/java/com/android/server/wm/Task.java7
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd1
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt2
-rw-r--r--services/java/com/android/server/SystemServer.java3
-rw-r--r--services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt2
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java125
-rw-r--r--services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java32
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java14
-rw-r--r--services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java69
-rw-r--r--services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java160
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java116
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java38
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java33
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java59
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java551
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java40
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java12
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java44
-rw-r--r--tools/xmlpersistence/src/main/kotlin/Generator.kt1
977 files changed, 30644 insertions, 16000 deletions
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index 7e814af3451d..1403ba2744b3 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -432,8 +432,9 @@ public class AnimationHandler {
/**
* Callbacks that receives notifications for animation timing and frame commit timing.
+ * @hide
*/
- interface AnimationFrameCallback {
+ public interface AnimationFrameCallback {
/**
* Run animation based on the frame time.
* @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e8379205d55f..8b41aa4a7e7d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8468,8 +8468,8 @@ public class Notification implements Parcelable
}
int maxAvatarSize = resources.getDimensionPixelSize(
- isLowRam ? R.dimen.notification_person_icon_max_size
- : R.dimen.notification_person_icon_max_size_low_ram);
+ isLowRam ? R.dimen.notification_person_icon_max_size_low_ram
+ : R.dimen.notification_person_icon_max_size);
if (mUser != null && mUser.getIcon() != null) {
mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize);
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index e022ca306674..0ea53ce52a52 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2080,6 +2080,21 @@ public class WallpaperManager {
}
/**
+ * Set the live wallpaper for the given screen(s).
+ *
+ * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
+ * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
+ * another user's wallpaper.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
+ public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
+ @SetWallpaperFlags int which) {
+ return setWallpaperComponent(name);
+ }
+
+ /**
* Set the display position of the current wallpaper within any larger space, when
* that wallpaper is visible behind the given window. The X and Y offsets
* are floating point numbers ranging from 0 to 1, representing where the
diff --git a/core/java/android/app/servertransaction/PendingTransactionActions.java b/core/java/android/app/servertransaction/PendingTransactionActions.java
index a47fe821cd01..81747782cab2 100644
--- a/core/java/android/app/servertransaction/PendingTransactionActions.java
+++ b/core/java/android/app/servertransaction/PendingTransactionActions.java
@@ -25,11 +25,12 @@ import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.TransactionTooLargeException;
import android.util.Log;
-import android.util.LogWriter;
import android.util.Slog;
import com.android.internal.util.IndentingPrintWriter;
+import java.io.StringWriter;
+
/**
* Container that has data pending to be used at later stages of
* {@link android.app.servertransaction.ClientTransaction}.
@@ -134,6 +135,16 @@ public class PendingTransactionActions {
mDescription = description;
}
+ private String collectBundleStates() {
+ final StringWriter writer = new StringWriter();
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ pw.println("Bundle stats:");
+ Bundle.dumpStats(pw, mState);
+ pw.println("PersistableBundle stats:");
+ Bundle.dumpStats(pw, mPersistentState);
+ return writer.toString().stripTrailing();
+ }
+
@Override
public void run() {
// Tell activity manager we have been stopped.
@@ -142,19 +153,24 @@ public class PendingTransactionActions {
// TODO(lifecycler): Use interface callback instead of AMS.
ActivityClient.getInstance().activityStopped(
mActivity.token, mState, mPersistentState, mDescription);
- } catch (RuntimeException ex) {
- // Dump statistics about bundle to help developers debug
- final LogWriter writer = new LogWriter(Log.WARN, TAG);
- final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- pw.println("Bundle stats:");
- Bundle.dumpStats(pw, mState);
- pw.println("PersistableBundle stats:");
- Bundle.dumpStats(pw, mPersistentState);
-
- if (ex.getCause() instanceof TransactionTooLargeException
- && mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) {
- Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex);
- return;
+ } catch (RuntimeException runtimeException) {
+ // Collect the statistics about bundle
+ final String bundleStats = collectBundleStates();
+
+ RuntimeException ex = runtimeException;
+ if (ex.getCause() instanceof TransactionTooLargeException) {
+ // Embed the stats into exception message to help developers debug if the
+ // transaction size is too large.
+ final String message = ex.getMessage() + "\n" + bundleStats;
+ ex = new RuntimeException(message, ex.getCause());
+ if (mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) {
+ Log.e(TAG, "App sent too much data in instance state, so it was ignored",
+ ex);
+ return;
+ }
+ } else {
+ // Otherwise, dump the stats anyway.
+ Log.w(TAG, bundleStats);
}
throw ex;
}
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index cc303fb1f413..2dced96d3583 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -329,6 +329,22 @@ public class AppWidgetHost {
}
/**
+ * Set the visibiity of all widgets associated with this host to hidden
+ *
+ * @hide
+ */
+ public void setAppWidgetHidden() {
+ if (sService == null) {
+ return;
+ }
+ try {
+ sService.setAppWidgetHidden(mContextOpPackageName, mHostId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("System server dead?", e);
+ }
+ }
+
+ /**
* Set the host's interaction handler.
*
* @hide
@@ -418,14 +434,7 @@ public class AppWidgetHost {
AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
view.setInteractionHandler(mInteractionHandler);
view.setAppWidget(appWidgetId, appWidget);
- addListener(appWidgetId, view);
- RemoteViews views;
- try {
- views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
- } catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
- view.updateAppWidget(views);
+ setListener(appWidgetId, view);
return view;
}
@@ -513,13 +522,19 @@ public class AppWidgetHost {
* The AppWidgetHost retains a pointer to the newly-created listener.
* @param appWidgetId The ID of the app widget for which to add the listener
* @param listener The listener interface that deals with actions towards the widget view
- *
* @hide
*/
- public void addListener(int appWidgetId, @NonNull AppWidgetHostListener listener) {
+ public void setListener(int appWidgetId, @NonNull AppWidgetHostListener listener) {
synchronized (mListeners) {
mListeners.put(appWidgetId, listener);
}
+ RemoteViews views = null;
+ try {
+ views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ listener.updateAppWidget(views);
}
/**
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 0f6010fffcb6..2a47851764da 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -272,6 +272,9 @@ public final class CameraExtensionCharacteristics {
@Override
public void onServiceConnected(ComponentName component, IBinder binder) {
mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder);
+ if (mProxy == null) {
+ throw new IllegalStateException("Camera Proxy service is null");
+ }
try {
mSupportsAdvancedExtensions = mProxy.advancedExtensionsSupported();
} catch (RemoteException e) {
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 5ca0da2d3f97..8afd6de235a0 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -335,4 +335,16 @@ public abstract class PowerManagerInternal {
/** Allows power button to intercept a power key button press. */
public abstract boolean interceptPowerKeyDown(KeyEvent event);
+
+ /**
+ * Internal version of {@link android.os.PowerManager#nap} which allows for napping while the
+ * device is not awake.
+ */
+ public abstract void nap(long eventTime, boolean allowWake);
+
+ /**
+ * Returns true if ambient display is suppressed by any app with any token. This method will
+ * return false if ambient display is not available.
+ */
+ public abstract boolean isAmbientDisplaySuppressed();
}
diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java
index f6a7c8eb8c4b..a2fa1392b079 100644
--- a/core/java/android/service/dreams/DreamActivity.java
+++ b/core/java/android/service/dreams/DreamActivity.java
@@ -44,6 +44,8 @@ import android.text.TextUtils;
public class DreamActivity extends Activity {
static final String EXTRA_CALLBACK = "binder";
static final String EXTRA_DREAM_TITLE = "title";
+ @Nullable
+ private DreamService.DreamActivityCallbacks mCallback;
public DreamActivity() {}
@@ -57,11 +59,19 @@ public class DreamActivity extends Activity {
}
final Bundle extras = getIntent().getExtras();
- final DreamService.DreamActivityCallback callback =
- (DreamService.DreamActivityCallback) extras.getBinder(EXTRA_CALLBACK);
+ mCallback = (DreamService.DreamActivityCallbacks) extras.getBinder(EXTRA_CALLBACK);
- if (callback != null) {
- callback.onActivityCreated(this);
+ if (mCallback != null) {
+ mCallback.onActivityCreated(this);
}
}
+
+ @Override
+ public void onDestroy() {
+ if (mCallback != null) {
+ mCallback.onActivityDestroyed();
+ }
+
+ super.onDestroy();
+ }
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 3c1fef02f9ba..32bdf7962273 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1047,7 +1047,7 @@ public class DreamService extends Service implements Window.Callback {
}
if (mDreamToken == null) {
- Slog.w(mTag, "Finish was called before the dream was attached.");
+ if (mDebug) Slog.v(mTag, "finish() called when not attached.");
stopSelf();
return;
}
@@ -1295,7 +1295,7 @@ public class DreamService extends Service implements Window.Callback {
Intent i = new Intent(this, DreamActivity.class);
i.setPackage(getApplicationContext().getPackageName());
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallback(mDreamToken));
+ i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));
final ServiceInfo serviceInfo = fetchServiceInfo(this,
new ComponentName(this, getClass()));
i.putExtra(DreamActivity.EXTRA_DREAM_TITLE, fetchDreamLabel(this, serviceInfo));
@@ -1488,10 +1488,10 @@ public class DreamService extends Service implements Window.Callback {
}
/** @hide */
- final class DreamActivityCallback extends Binder {
+ final class DreamActivityCallbacks extends Binder {
private final IBinder mActivityDreamToken;
- DreamActivityCallback(IBinder token) {
+ DreamActivityCallbacks(IBinder token) {
mActivityDreamToken = token;
}
@@ -1516,6 +1516,12 @@ public class DreamService extends Service implements Window.Callback {
mActivity = activity;
onWindowCreated(activity.getWindow());
}
+
+ // If DreamActivity is destroyed, wake up from Dream.
+ void onActivityDestroyed() {
+ mActivity = null;
+ onDestroy();
+ }
}
/**
diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl
index 56e2486dd626..f46c60fc4f7a 100644
--- a/core/java/android/service/wallpaper/IWallpaperService.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperService.aidl
@@ -25,6 +25,6 @@ import android.service.wallpaper.IWallpaperConnection;
oneway interface IWallpaperService {
void attach(IWallpaperConnection connection,
IBinder windowToken, int windowType, boolean isPreview,
- int reqWidth, int reqHeight, in Rect padding, int displayId);
+ int reqWidth, int reqHeight, in Rect padding, int displayId, int which);
void detach();
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 01749c0bebb9..e5792a9ef4e2 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -18,6 +18,7 @@ package android.service.wallpaper;
import static android.app.WallpaperManager.COMMAND_FREEZE;
import static android.app.WallpaperManager.COMMAND_UNFREEZE;
+import static android.app.WallpaperManager.SetWallpaperFlags;
import static android.graphics.Matrix.MSCALE_X;
import static android.graphics.Matrix.MSCALE_Y;
import static android.graphics.Matrix.MSKEW_X;
@@ -2427,7 +2428,7 @@ public abstract class WallpaperService extends Service {
@Override
public void attach(IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
- int displayId) {
+ int displayId, @SetWallpaperFlags int which) {
mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken,
windowType, isPreview, reqWidth, reqHeight, padding, displayId);
}
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 44f419a8097d..e407707231ca 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -241,6 +241,8 @@ public class RemoteAnimationTarget implements Parcelable {
*/
public boolean willShowImeOnTarget;
+ public int rotationChange;
+
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
Rect localBounds, Rect screenSpaceBounds,
@@ -302,6 +304,7 @@ public class RemoteAnimationTarget implements Parcelable {
backgroundColor = in.readInt();
showBackdrop = in.readBoolean();
willShowImeOnTarget = in.readBoolean();
+ rotationChange = in.readInt();
}
public void setShowBackdrop(boolean shouldShowBackdrop) {
@@ -316,6 +319,14 @@ public class RemoteAnimationTarget implements Parcelable {
return willShowImeOnTarget;
}
+ public void setRotationChange(int rotationChange) {
+ this.rotationChange = rotationChange;
+ }
+
+ public int getRotationChange() {
+ return rotationChange;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -345,6 +356,7 @@ public class RemoteAnimationTarget implements Parcelable {
dest.writeInt(backgroundColor);
dest.writeBoolean(showBackdrop);
dest.writeBoolean(willShowImeOnTarget);
+ dest.writeInt(rotationChange);
}
public void dump(PrintWriter pw, String prefix) {
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
new file mode 100644
index 000000000000..dd4385c8f50c
--- /dev/null
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.util.FloatProperty;
+
+import com.android.internal.dynamicanimation.animation.SpringAnimation;
+import com.android.internal.dynamicanimation.animation.SpringForce;
+
+/**
+ * An animator that drives the predictive back progress with a spring.
+ *
+ * The back gesture's latest touch point and committal state determines the final position of
+ * the spring. The continuous movement of the spring is used to produce {@link BackEvent}s with
+ * smoothly transitioning progress values.
+ *
+ * @hide
+ */
+public class BackProgressAnimator {
+ /**
+ * A factor to scale the input progress by, so that it works better with the spring.
+ * We divide the output progress by this value before sending it to apps, so that apps
+ * always receive progress values in [0, 1].
+ */
+ private static final float SCALE_FACTOR = 100f;
+ private final SpringAnimation mSpring;
+ private ProgressCallback mCallback;
+ private float mProgress = 0;
+ private BackEvent mLastBackEvent;
+ private boolean mStarted = false;
+
+ private void setProgress(float progress) {
+ mProgress = progress;
+ }
+
+ private float getProgress() {
+ return mProgress;
+ }
+
+ private static final FloatProperty<BackProgressAnimator> PROGRESS_PROP =
+ new FloatProperty<BackProgressAnimator>("progress") {
+ @Override
+ public void setValue(BackProgressAnimator animator, float value) {
+ animator.setProgress(value);
+ animator.updateProgressValue(value);
+ }
+
+ @Override
+ public Float get(BackProgressAnimator object) {
+ return object.getProgress();
+ }
+ };
+
+
+ /** A callback to be invoked when there's a progress value update from the animator. */
+ public interface ProgressCallback {
+ /** Called when there's a progress value update. */
+ void onProgressUpdate(BackEvent event);
+ }
+
+ public BackProgressAnimator() {
+ mSpring = new SpringAnimation(this, PROGRESS_PROP);
+ mSpring.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+ }
+
+ /**
+ * Sets a new target position for the back progress.
+ *
+ * @param event the {@link BackEvent} containing the latest target progress.
+ */
+ public void onBackProgressed(BackEvent event) {
+ if (!mStarted) {
+ return;
+ }
+ mLastBackEvent = event;
+ mSpring.animateToFinalPosition(event.getProgress() * SCALE_FACTOR);
+ }
+
+ /**
+ * Starts the back progress animation.
+ *
+ * @param event the {@link BackEvent} that started the gesture.
+ * @param callback the back callback to invoke for the gesture. It will receive back progress
+ * dispatches as the progress animation updates.
+ */
+ public void onBackStarted(BackEvent event, ProgressCallback callback) {
+ reset();
+ mLastBackEvent = event;
+ mCallback = callback;
+ mStarted = true;
+ }
+
+ /**
+ * Resets the back progress animation. This should be called when back is invoked or cancelled.
+ */
+ public void reset() {
+ mSpring.animateToFinalPosition(0);
+ if (mSpring.canSkipToEnd()) {
+ mSpring.skipToEnd();
+ } else {
+ // Should never happen.
+ mSpring.cancel();
+ }
+ mStarted = false;
+ mLastBackEvent = null;
+ mCallback = null;
+ mProgress = 0;
+ }
+
+ private void updateProgressValue(float progress) {
+ if (mLastBackEvent == null || mCallback == null || !mStarted) {
+ return;
+ }
+ mCallback.onProgressUpdate(
+ new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
+ progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(),
+ mLastBackEvent.getDepartingAnimationTarget()));
+ }
+
+}
diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl
index 47796de11dd5..6af8ddda3a62 100644
--- a/core/java/android/window/IOnBackInvokedCallback.aidl
+++ b/core/java/android/window/IOnBackInvokedCallback.aidl
@@ -28,17 +28,18 @@ import android.window.BackEvent;
oneway interface IOnBackInvokedCallback {
/**
* Called when a back gesture has been started, or back button has been pressed down.
- * Wraps {@link OnBackInvokedCallback#onBackStarted()}.
+ * Wraps {@link OnBackInvokedCallback#onBackStarted(BackEvent)}.
+ *
+ * @param backEvent The {@link BackEvent} containing information about the touch or button press.
*/
- void onBackStarted();
+ void onBackStarted(in BackEvent backEvent);
/**
* Called on back gesture progress.
- * Wraps {@link OnBackInvokedCallback#onBackProgressed()}.
+ * Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}.
*
- * @param touchX Absolute X location of the touch point.
- * @param touchY Absolute Y location of the touch point.
- * @param progress Value between 0 and 1 on how far along the back gesture is.
+ * @param backEvent The {@link BackEvent} containing information about the latest touch point
+ * and the progress that the back animation should seek to.
*/
void onBackProgressed(in BackEvent backEvent);
diff --git a/core/java/android/window/OnBackAnimationCallback.java b/core/java/android/window/OnBackAnimationCallback.java
index 1a37e57df403..582308436b02 100644
--- a/core/java/android/window/OnBackAnimationCallback.java
+++ b/core/java/android/window/OnBackAnimationCallback.java
@@ -40,10 +40,12 @@ import android.view.View;
* @hide
*/
public interface OnBackAnimationCallback extends OnBackInvokedCallback {
- /**
- * Called when a back gesture has been started, or back button has been pressed down.
- */
- default void onBackStarted() { }
+ /**
+ * Called when a back gesture has been started, or back button has been pressed down.
+ *
+ * @param backEvent An {@link BackEvent} object describing the progress event.
+ */
+ default void onBackStarted(@NonNull BackEvent backEvent) {}
/**
* Called on back gesture progress.
diff --git a/core/java/android/window/OnBackInvokedCallback.java b/core/java/android/window/OnBackInvokedCallback.java
index 6e2d4f9edbc1..62c41bfb0681 100644
--- a/core/java/android/window/OnBackInvokedCallback.java
+++ b/core/java/android/window/OnBackInvokedCallback.java
@@ -16,6 +16,7 @@
package android.window;
+import android.annotation.NonNull;
import android.app.Activity;
import android.app.Dialog;
import android.view.Window;
@@ -41,8 +42,35 @@ import android.view.Window;
@SuppressWarnings("deprecation")
public interface OnBackInvokedCallback {
/**
+ * Called when a back gesture has been started, or back button has been pressed down.
+ *
+ * @param backEvent The {@link BackEvent} containing information about the touch or
+ * button press.
+ *
+ * @hide
+ */
+ default void onBackStarted(@NonNull BackEvent backEvent) {}
+
+ /**
+ * Called when a back gesture has been progressed.
+ *
+ * @param backEvent The {@link BackEvent} containing information about the latest touch point
+ * and the progress that the back animation should seek to.
+ *
+ * @hide
+ */
+ default void onBackProgressed(@NonNull BackEvent backEvent) {}
+
+ /**
* Called when a back gesture has been completed and committed, or back button pressed
* has been released and committed.
*/
void onBackInvoked();
+
+ /**
+ * Called when a back gesture or button press has been cancelled.
+ *
+ * @hide
+ */
+ default void onBackCancelled() {}
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 0730f3ddf8ac..fda39c14dac7 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -218,19 +218,24 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
public Checker getChecker() {
return mChecker;
}
+ @NonNull
+ private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference<OnBackInvokedCallback> mCallback;
+
OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
mCallback = new WeakReference<>(callback);
}
@Override
- public void onBackStarted() {
+ public void onBackStarted(BackEvent backEvent) {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
- callback.onBackStarted();
+ mProgressAnimator.onBackStarted(backEvent, event ->
+ callback.onBackProgressed(event));
+ callback.onBackStarted(backEvent);
}
});
}
@@ -240,7 +245,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
- callback.onBackProgressed(backEvent);
+ mProgressAnimator.onBackProgressed(backEvent);
}
});
}
@@ -248,6 +253,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override
public void onBackCancelled() {
Handler.getMain().post(() -> {
+ mProgressAnimator.reset();
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
callback.onBackCancelled();
@@ -258,6 +264,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override
public void onBackInvoked() throws RemoteException {
Handler.getMain().post(() -> {
+ mProgressAnimator.reset();
final OnBackInvokedCallback callback = mCallback.get();
if (callback == null) {
return;
diff --git a/core/java/com/android/internal/app/AppLocaleCollector.java b/core/java/com/android/internal/app/AppLocaleCollector.java
new file mode 100644
index 000000000000..65e8c646e17d
--- /dev/null
+++ b/core/java/com/android/internal/app/AppLocaleCollector.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
+import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.LocaleList;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+/** The Locale data collector for per-app language. */
+class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+ private static final String TAG = AppLocaleCollector.class.getSimpleName();
+ private final Context mContext;
+ private final String mAppPackageName;
+ private final LocaleStore.LocaleInfo mAppCurrentLocale;
+
+ AppLocaleCollector(Context context, String appPackageName) {
+ mContext = context;
+ mAppPackageName = appPackageName;
+ mAppCurrentLocale = LocaleStore.getAppCurrentLocaleInfo(
+ mContext, mAppPackageName);
+ }
+
+ @Override
+ public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
+ HashSet<String> langTagsToIgnore = new HashSet<>();
+
+ LocaleList systemLangList = LocaleList.getDefault();
+ for(int i = 0; i < systemLangList.size(); i++) {
+ langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
+ }
+
+ if (mAppCurrentLocale != null) {
+ langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag());
+ }
+ return langTagsToIgnore;
+ }
+
+ @Override
+ public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+ boolean translatedOnly, boolean isForCountryMode) {
+ AppLocaleStore.AppLocaleResult result =
+ AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName);
+ Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
+ Set<LocaleStore.LocaleInfo> appLocaleList = new HashSet<>();
+ Set<LocaleStore.LocaleInfo> systemLocaleList;
+ boolean shouldShowList =
+ result.mLocaleStatus == GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG
+ || result.mLocaleStatus == GET_SUPPORTED_LANGUAGE_FROM_ASSET;
+
+ // Get system supported locale list
+ if (isForCountryMode) {
+ systemLocaleList = LocaleStore.getLevelLocales(mContext,
+ langTagsToIgnore, parent, translatedOnly);
+ } else {
+ systemLocaleList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
+ null /* no parent */, translatedOnly);
+ }
+
+ // Add current app locale
+ if (mAppCurrentLocale != null && !isForCountryMode) {
+ appLocaleList.add(mAppCurrentLocale);
+ }
+
+ // Add current system language into suggestion list
+ for(LocaleStore.LocaleInfo localeInfo:
+ LocaleStore.getSystemCurrentLocaleInfo()) {
+ boolean isNotCurrentLocale = mAppCurrentLocale == null
+ || !localeInfo.getLocale().equals(mAppCurrentLocale.getLocale());
+ if (!isForCountryMode && isNotCurrentLocale) {
+ appLocaleList.add(localeInfo);
+ }
+ }
+
+ // Add the languages that included in system supported locale
+ if (shouldShowList) {
+ appLocaleList.addAll(filterTheLanguagesNotIncludedInSystemLocale(
+ systemLocaleList, result.mAppSupportedLocales));
+ }
+
+ // Add "system language" option
+ if (!isForCountryMode && shouldShowList) {
+ appLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo(
+ mAppCurrentLocale == null));
+ }
+
+ if (Build.isDebuggable()) {
+ Log.d(TAG, "App locale list: " + appLocaleList);
+ }
+
+ return appLocaleList;
+ }
+
+ @Override
+ public boolean hasSpecificPackageName() {
+ return true;
+ }
+
+ private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotIncludedInSystemLocale(
+ Set<LocaleStore.LocaleInfo> systemLocaleList,
+ HashSet<Locale> appSupportedLocales) {
+ Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>();
+
+ for(LocaleStore.LocaleInfo li: systemLocaleList) {
+ if (appSupportedLocales.contains(li.getLocale())) {
+ filteredList.add(li);
+ } else {
+ for(Locale l: appSupportedLocales) {
+ if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
+ filteredList.add(li);
+ break;
+ }
+ }
+ }
+ }
+ return filteredList;
+ }
+}
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 965895f08d6e..3efd279c2639 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -16,16 +16,12 @@
package com.android.internal.app;
-import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus;
-
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.content.Context;
import android.os.Bundle;
-import android.os.LocaleList;
import android.text.TextUtils;
-import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -36,7 +32,6 @@ import android.widget.SearchView;
import com.android.internal.R;
-import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
@@ -54,6 +49,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
private SuggestedLocaleAdapter mAdapter;
private LocaleSelectedListener mListener;
+ private LocaleCollectorBase mLocalePickerCollector;
private Set<LocaleStore.LocaleInfo> mLocaleList;
private LocaleStore.LocaleInfo mParentLocale;
private boolean mTranslatedOnly = false;
@@ -62,7 +58,6 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
private boolean mPreviousSearchHadFocus = false;
private int mFirstVisiblePosition = 0;
private int mTopDistance = 0;
- private String mAppPackageName;
private CharSequence mTitle = null;
private OnActionExpandListener mOnActionExpandListener;
@@ -79,31 +74,50 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
void onLocaleSelected(LocaleStore.LocaleInfo locale);
}
- private static LocalePickerWithRegion createCountryPicker(Context context,
+ /**
+ * The interface which provides the locale list.
+ */
+ interface LocaleCollectorBase {
+ /** Gets the ignored locale list. */
+ HashSet<String> getIgnoredLocaleList(boolean translatedOnly);
+
+ /** Gets the supported locale list. */
+ Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+ boolean translatedOnly, boolean isForCountryMode);
+
+ /** Indicates if the class work for specific package. */
+ boolean hasSpecificPackageName();
+ }
+
+ private static LocalePickerWithRegion createCountryPicker(
LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
- boolean translatedOnly, String appPackageName,
- OnActionExpandListener onActionExpandListener) {
+ boolean translatedOnly, OnActionExpandListener onActionExpandListener,
+ LocaleCollectorBase localePickerCollector) {
LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
localePicker.setOnActionExpandListener(onActionExpandListener);
- boolean shouldShowTheList = localePicker.setListener(context, listener, parent,
- translatedOnly, appPackageName);
+ boolean shouldShowTheList = localePicker.setListener(listener, parent,
+ translatedOnly, localePickerCollector);
return shouldShowTheList ? localePicker : null;
}
public static LocalePickerWithRegion createLanguagePicker(Context context,
LocaleSelectedListener listener, boolean translatedOnly) {
- LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
- localePicker.setListener(context, listener, /* parent */ null, translatedOnly, null);
- return localePicker;
+ return createLanguagePicker(context, listener, translatedOnly, null, null);
}
public static LocalePickerWithRegion createLanguagePicker(Context context,
LocaleSelectedListener listener, boolean translatedOnly, String appPackageName,
OnActionExpandListener onActionExpandListener) {
+ LocaleCollectorBase localePickerController;
+ if (TextUtils.isEmpty(appPackageName)) {
+ localePickerController = new SystemLocaleCollector(context);
+ } else {
+ localePickerController = new AppLocaleCollector(context, appPackageName);
+ }
LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
localePicker.setOnActionExpandListener(onActionExpandListener);
- localePicker.setListener(
- context, listener, /* parent */ null, translatedOnly, appPackageName);
+ localePicker.setListener(listener, /* parent */ null, translatedOnly,
+ localePickerController);
return localePicker;
}
@@ -120,109 +134,23 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
* In this case we don't even show the list, we call the listener with that locale,
* "pretending" it was selected, and return false.</p>
*/
- private boolean setListener(Context context, LocaleSelectedListener listener,
- LocaleStore.LocaleInfo parent, boolean translatedOnly, String appPackageName) {
+ private boolean setListener(LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
+ boolean translatedOnly, LocaleCollectorBase localePickerController) {
this.mParentLocale = parent;
this.mListener = listener;
this.mTranslatedOnly = translatedOnly;
- this.mAppPackageName = appPackageName;
+ this.mLocalePickerCollector = localePickerController;
setRetainInstance(true);
- final HashSet<String> langTagsToIgnore = new HashSet<>();
- LocaleStore.LocaleInfo appCurrentLocale =
- LocaleStore.getAppCurrentLocaleInfo(context, appPackageName);
- boolean isForCountryMode = parent != null;
-
- if (!TextUtils.isEmpty(appPackageName) && !isForCountryMode) {
- // Filter current system locale to add them into suggestion
- LocaleList systemLangList = LocaleList.getDefault();
- for(int i = 0; i < systemLangList.size(); i++) {
- langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
- }
-
- if (appCurrentLocale != null) {
- Log.d(TAG, "appCurrentLocale: " + appCurrentLocale.getLocale().toLanguageTag());
- langTagsToIgnore.add(appCurrentLocale.getLocale().toLanguageTag());
- } else {
- Log.d(TAG, "appCurrentLocale is null");
- }
- } else if (!translatedOnly) {
- final LocaleList userLocales = LocalePicker.getLocales();
- final String[] langTags = userLocales.toLanguageTags().split(",");
- Collections.addAll(langTagsToIgnore, langTags);
- }
+ mLocaleList = localePickerController.getSupportedLocaleList(
+ parent, translatedOnly, parent != null);
- if (isForCountryMode) {
- mLocaleList = LocaleStore.getLevelLocales(context,
- langTagsToIgnore, parent, translatedOnly);
- if (mLocaleList.size() <= 1) {
- if (listener != null && (mLocaleList.size() == 1)) {
- listener.onLocaleSelected(mLocaleList.iterator().next());
- }
- return false;
- }
+ if (parent != null && listener != null && mLocaleList.size() == 1) {
+ listener.onLocaleSelected(mLocaleList.iterator().next());
+ return false;
} else {
- mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore,
- null /* no parent */, translatedOnly);
+ return true;
}
- Log.d(TAG, "mLocaleList size: " + mLocaleList.size());
-
- // Adding current locale and system default option into suggestion list
- if(!TextUtils.isEmpty(appPackageName)) {
- if (appCurrentLocale != null && !isForCountryMode) {
- mLocaleList.add(appCurrentLocale);
- }
-
- AppLocaleStore.AppLocaleResult result =
- AppLocaleStore.getAppSupportedLocales(context, appPackageName);
- boolean shouldShowList =
- result.mLocaleStatus == LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG
- || result.mLocaleStatus == LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
-
- // Add current system language into suggestion list
- for(LocaleStore.LocaleInfo localeInfo: LocaleStore.getSystemCurrentLocaleInfo()) {
- boolean isNotCurrentLocale = appCurrentLocale == null
- || !localeInfo.getLocale().equals(appCurrentLocale.getLocale());
- if (!isForCountryMode && isNotCurrentLocale) {
- mLocaleList.add(localeInfo);
- }
- }
-
- // Filter the language not support in app
- mLocaleList = filterTheLanguagesNotSupportedInApp(
- shouldShowList, result.mAppSupportedLocales);
-
- Log.d(TAG, "mLocaleList after app-supported filter: " + mLocaleList.size());
-
- // Add "system language"
- if (!isForCountryMode && shouldShowList) {
- mLocaleList.add(LocaleStore.getSystemDefaultLocaleInfo(appCurrentLocale == null));
- }
- }
- return true;
- }
-
- private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotSupportedInApp(
- boolean shouldShowList, HashSet<Locale> supportedLocales) {
- Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>();
- if (!shouldShowList) {
- return filteredList;
- }
-
- for(LocaleStore.LocaleInfo li: mLocaleList) {
- if (supportedLocales.contains(li.getLocale())) {
- filteredList.add(li);
- } else {
- for(Locale l: supportedLocales) {
- if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
- filteredList.add(li);
- break;
- }
- }
- }
- }
-
- return filteredList;
}
private void returnToParentFrame() {
@@ -246,7 +174,9 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
mTitle = getActivity().getTitle();
final boolean countryMode = mParentLocale != null;
final Locale sortingLocale = countryMode ? mParentLocale.getLocale() : Locale.getDefault();
- mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, mAppPackageName);
+ final boolean hasSpecificPackageName =
+ mLocalePickerCollector != null && mLocalePickerCollector.hasSpecificPackageName();
+ mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode, hasSpecificPackageName);
final LocaleHelper.LocaleInfoComparator comp =
new LocaleHelper.LocaleInfoComparator(sortingLocale, countryMode);
mAdapter.sort(comp);
@@ -321,8 +251,8 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
returnToParentFrame();
} else {
LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker(
- getContext(), mListener, locale, mTranslatedOnly /* translate only */,
- mAppPackageName, mOnActionExpandListener);
+ mListener, locale, mTranslatedOnly /* translate only */,
+ mOnActionExpandListener, this.mLocalePickerCollector);
if (selector != null) {
getFragmentManager().beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
@@ -340,7 +270,8 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
inflater.inflate(R.menu.language_selection_list, menu);
final MenuItem searchMenuItem = menu.findItem(R.id.locale_search_menu);
- if (!TextUtils.isEmpty(mAppPackageName) && mOnActionExpandListener != null) {
+ if (mLocalePickerCollector.hasSpecificPackageName()
+ && mOnActionExpandListener != null) {
searchMenuItem.setOnActionExpandListener(mOnActionExpandListener);
}
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 4a1f7eb06c40..42b46cda6ba3 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -647,15 +647,16 @@ public class ResolverListAdapter extends BaseAdapter {
if (info instanceof DisplayResolveInfo) {
DisplayResolveInfo dri = (DisplayResolveInfo) info;
- boolean hasLabel = dri.hasDisplayLabel();
- holder.bindLabel(
- dri.getDisplayLabel(),
- dri.getExtendedInfo(),
- hasLabel && alwaysShowSubLabel());
- holder.bindIcon(info);
- if (!hasLabel) {
+ if (dri.hasDisplayLabel()) {
+ holder.bindLabel(
+ dri.getDisplayLabel(),
+ dri.getExtendedInfo(),
+ alwaysShowSubLabel());
+ } else {
+ holder.bindLabel("", "", false);
loadLabel(dri);
}
+ holder.bindIcon(info);
if (!dri.hasDisplayIcon()) {
loadIcon(dri);
}
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index 1be1247b7cc0..5ed0e8bc7883 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -70,17 +70,17 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable {
private Locale mDisplayLocale = null;
// used to potentially cache a modified Context that uses mDisplayLocale
private Context mContextOverride = null;
- private String mAppPackageName;
+ private boolean mHasSpecificAppPackageName;
public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode) {
- this(localeOptions, countryMode, null);
+ this(localeOptions, countryMode, false);
}
public SuggestedLocaleAdapter(Set<LocaleStore.LocaleInfo> localeOptions, boolean countryMode,
- String appPackageName) {
+ boolean hasSpecificAppPackageName) {
mCountryMode = countryMode;
mLocaleOptions = new ArrayList<>(localeOptions.size());
- mAppPackageName = appPackageName;
+ mHasSpecificAppPackageName = hasSpecificAppPackageName;
for (LocaleStore.LocaleInfo li : localeOptions) {
if (li.isSuggested()) {
@@ -134,7 +134,7 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable {
@Override
public int getViewTypeCount() {
- if (!TextUtils.isEmpty(mAppPackageName) && showHeaders()) {
+ if (mHasSpecificAppPackageName && showHeaders()) {
// Two headers, 1 "System language", 1 current locale
return APP_LANGUAGE_PICKER_TYPE_COUNT;
} else if (showHeaders()) {
diff --git a/core/java/com/android/internal/app/SystemLocaleCollector.java b/core/java/com/android/internal/app/SystemLocaleCollector.java
new file mode 100644
index 000000000000..9a6d4c192fdc
--- /dev/null
+++ b/core/java/com/android/internal/app/SystemLocaleCollector.java
@@ -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.internal.app;
+
+import android.content.Context;
+import android.os.LocaleList;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/** The Locale data collector for System language. */
+class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+ private final Context mContext;
+
+ SystemLocaleCollector(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
+ HashSet<String> ignoreList = new HashSet<>();
+ if (!translatedOnly) {
+ final LocaleList userLocales = LocalePicker.getLocales();
+ final String[] langTags = userLocales.toLanguageTags().split(",");
+ Collections.addAll(ignoreList, langTags);
+ }
+ return ignoreList;
+ }
+
+ @Override
+ public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
+ boolean translatedOnly, boolean isForCountryMode) {
+ Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
+ Set<LocaleStore.LocaleInfo> localeList;
+
+ if (isForCountryMode) {
+ localeList = LocaleStore.getLevelLocales(mContext,
+ langTagsToIgnore, parent, translatedOnly);
+ } else {
+ localeList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
+ null /* no parent */, translatedOnly);
+ }
+ return localeList;
+ }
+
+
+ @Override
+ public boolean hasSpecificPackageName() {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 2d68cb472fa3..51b56dbf582b 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -45,6 +45,7 @@ interface IAppWidgetService {
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
RemoteViews getAppWidgetViews(String callingPackage, int appWidgetId);
int[] getAppWidgetIdsForHost(String callingPackage, int hostId);
+ void setAppWidgetHidden(in String callingPackage, int hostId);
IntentSender createAppWidgetConfigIntentSender(String callingPackage, int appWidgetId,
int intentFlags);
diff --git a/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java b/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java
new file mode 100644
index 000000000000..d4fe7c8d7f36
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dynamicanimation.animation;
+
+import android.animation.AnimationHandler;
+import android.animation.ValueAnimator;
+import android.annotation.FloatRange;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.os.Looper;
+import android.util.AndroidRuntimeException;
+import android.util.FloatProperty;
+import android.view.View;
+
+import java.util.ArrayList;
+
+/**
+ * This class is the base class of physics-based animations. It manages the animation's
+ * lifecycle such as {@link #start()} and {@link #cancel()}. This base class also handles the common
+ * setup for all the subclass animations. For example, DynamicAnimation supports adding
+ * {@link OnAnimationEndListener} and {@link OnAnimationUpdateListener} so that the important
+ * animation events can be observed through the callbacks. The start conditions for any subclass of
+ * DynamicAnimation can be set using {@link #setStartValue(float)} and
+ * {@link #setStartVelocity(float)}.
+ *
+ * @param <T> subclass of DynamicAnimation
+ */
+public abstract class DynamicAnimation<T extends DynamicAnimation<T>>
+ implements AnimationHandler.AnimationFrameCallback {
+
+ /**
+ * ViewProperty holds the access of a property of a {@link View}. When an animation is
+ * created with a {@link ViewProperty} instance, the corresponding property value of the view
+ * will be updated through this ViewProperty instance.
+ */
+ public abstract static class ViewProperty extends FloatProperty<View> {
+ private ViewProperty(String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * View's translationX property.
+ */
+ public static final ViewProperty TRANSLATION_X = new ViewProperty("translationX") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setTranslationX(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getTranslationX();
+ }
+ };
+
+ /**
+ * View's translationY property.
+ */
+ public static final ViewProperty TRANSLATION_Y = new ViewProperty("translationY") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setTranslationY(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getTranslationY();
+ }
+ };
+
+ /**
+ * View's translationZ property.
+ */
+ public static final ViewProperty TRANSLATION_Z = new ViewProperty("translationZ") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setTranslationZ(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getTranslationZ();
+ }
+ };
+
+ /**
+ * View's scaleX property.
+ */
+ public static final ViewProperty SCALE_X = new ViewProperty("scaleX") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setScaleX(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getScaleX();
+ }
+ };
+
+ /**
+ * View's scaleY property.
+ */
+ public static final ViewProperty SCALE_Y = new ViewProperty("scaleY") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setScaleY(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getScaleY();
+ }
+ };
+
+ /**
+ * View's rotation property.
+ */
+ public static final ViewProperty ROTATION = new ViewProperty("rotation") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setRotation(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getRotation();
+ }
+ };
+
+ /**
+ * View's rotationX property.
+ */
+ public static final ViewProperty ROTATION_X = new ViewProperty("rotationX") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setRotationX(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getRotationX();
+ }
+ };
+
+ /**
+ * View's rotationY property.
+ */
+ public static final ViewProperty ROTATION_Y = new ViewProperty("rotationY") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setRotationY(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getRotationY();
+ }
+ };
+
+ /**
+ * View's x property.
+ */
+ public static final ViewProperty X = new ViewProperty("x") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setX(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getX();
+ }
+ };
+
+ /**
+ * View's y property.
+ */
+ public static final ViewProperty Y = new ViewProperty("y") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setY(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getY();
+ }
+ };
+
+ /**
+ * View's z property.
+ */
+ public static final ViewProperty Z = new ViewProperty("z") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setZ(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getZ();
+ }
+ };
+
+ /**
+ * View's alpha property.
+ */
+ public static final ViewProperty ALPHA = new ViewProperty("alpha") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setAlpha(value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return view.getAlpha();
+ }
+ };
+
+ // Properties below are not RenderThread compatible
+ /**
+ * View's scrollX property.
+ */
+ public static final ViewProperty SCROLL_X = new ViewProperty("scrollX") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setScrollX((int) value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return (float) view.getScrollX();
+ }
+ };
+
+ /**
+ * View's scrollY property.
+ */
+ public static final ViewProperty SCROLL_Y = new ViewProperty("scrollY") {
+ @Override
+ public void setValue(View view, float value) {
+ view.setScrollY((int) value);
+ }
+
+ @Override
+ public Float get(View view) {
+ return (float) view.getScrollY();
+ }
+ };
+
+ /**
+ * The minimum visible change in pixels that can be visible to users.
+ */
+ @SuppressLint("MinMaxConstant")
+ public static final float MIN_VISIBLE_CHANGE_PIXELS = 1f;
+ /**
+ * The minimum visible change in degrees that can be visible to users.
+ */
+ @SuppressLint("MinMaxConstant")
+ public static final float MIN_VISIBLE_CHANGE_ROTATION_DEGREES = 1f / 10f;
+ /**
+ * The minimum visible change in alpha that can be visible to users.
+ */
+ @SuppressLint("MinMaxConstant")
+ public static final float MIN_VISIBLE_CHANGE_ALPHA = 1f / 256f;
+ /**
+ * The minimum visible change in scale that can be visible to users.
+ */
+ @SuppressLint("MinMaxConstant")
+ public static final float MIN_VISIBLE_CHANGE_SCALE = 1f / 500f;
+
+ // Use the max value of float to indicate an unset state.
+ private static final float UNSET = Float.MAX_VALUE;
+
+ // Multiplier to the min visible change value for value threshold
+ private static final float THRESHOLD_MULTIPLIER = 0.75f;
+
+ // Internal tracking for velocity.
+ float mVelocity = 0;
+
+ // Internal tracking for value.
+ float mValue = UNSET;
+
+ // Tracks whether start value is set. If not, the animation will obtain the value at the time
+ // of starting through the getter and use that as the starting value of the animation.
+ boolean mStartValueIsSet = false;
+
+ // Target to be animated.
+ final Object mTarget;
+
+ // View property id.
+ final FloatProperty mProperty;
+
+ // Package private tracking of animation lifecycle state. Visible to subclass animations.
+ boolean mRunning = false;
+
+ // Min and max values that defines the range of the animation values.
+ float mMaxValue = Float.MAX_VALUE;
+ float mMinValue = -mMaxValue;
+
+ // Last frame time. Always gets reset to -1 at the end of the animation.
+ private long mLastFrameTime = 0;
+
+ private float mMinVisibleChange;
+
+ // List of end listeners
+ private final ArrayList<OnAnimationEndListener> mEndListeners = new ArrayList<>();
+
+ // List of update listeners
+ private final ArrayList<OnAnimationUpdateListener> mUpdateListeners = new ArrayList<>();
+
+ // Animation handler used to schedule updates for this animation.
+ private AnimationHandler mAnimationHandler;
+
+ // Internal state for value/velocity pair.
+ static class MassState {
+ float mValue;
+ float mVelocity;
+ }
+
+ /**
+ * Creates a dynamic animation with the given FloatValueHolder instance.
+ *
+ * @param floatValueHolder the FloatValueHolder instance to be animated.
+ */
+ DynamicAnimation(final FloatValueHolder floatValueHolder) {
+ mTarget = null;
+ mProperty = new FloatProperty("FloatValueHolder") {
+ @Override
+ public Float get(Object object) {
+ return floatValueHolder.getValue();
+ }
+
+ @Override
+ public void setValue(Object object, float value) {
+ floatValueHolder.setValue(value);
+ }
+ };
+ mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS;
+ }
+
+ /**
+ * Creates a dynamic animation to animate the given property for the given {@link View}
+ *
+ * @param object the Object whose property is to be animated
+ * @param property the property to be animated
+ */
+
+ <K> DynamicAnimation(K object, FloatProperty<K> property) {
+ mTarget = object;
+ mProperty = property;
+ if (mProperty == ROTATION || mProperty == ROTATION_X
+ || mProperty == ROTATION_Y) {
+ mMinVisibleChange = MIN_VISIBLE_CHANGE_ROTATION_DEGREES;
+ } else if (mProperty == ALPHA) {
+ mMinVisibleChange = MIN_VISIBLE_CHANGE_ALPHA;
+ } else if (mProperty == SCALE_X || mProperty == SCALE_Y) {
+ mMinVisibleChange = MIN_VISIBLE_CHANGE_SCALE;
+ } else {
+ mMinVisibleChange = MIN_VISIBLE_CHANGE_PIXELS;
+ }
+ }
+
+ /**
+ * Sets the start value of the animation. If start value is not set, the animation will get
+ * the current value for the view's property, and use that as the start value.
+ *
+ * @param startValue start value for the animation
+ * @return the Animation whose start value is being set
+ */
+ @SuppressWarnings("unchecked")
+ public T setStartValue(float startValue) {
+ mValue = startValue;
+ mStartValueIsSet = true;
+ return (T) this;
+ }
+
+ /**
+ * Start velocity of the animation. Default velocity is 0. Unit: change in property per
+ * second (e.g. pixels per second, scale/alpha value change per second).
+ *
+ * <p>Note when using a fixed value as the start velocity (as opposed to getting the velocity
+ * through touch events), it is recommended to define such a value in dp/second and convert it
+ * to pixel/second based on the density of the screen to achieve a consistent look across
+ * different screens.
+ *
+ * <p>To convert from dp/second to pixel/second:
+ * <pre class="prettyprint">
+ * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond,
+ * getResources().getDisplayMetrics());
+ * </pre>
+ *
+ * @param startVelocity start velocity of the animation
+ * @return the Animation whose start velocity is being set
+ */
+ @SuppressWarnings("unchecked")
+ public T setStartVelocity(float startVelocity) {
+ mVelocity = startVelocity;
+ return (T) this;
+ }
+
+ /**
+ * Sets the max value of the animation. Animations will not animate beyond their max value.
+ * Whether or not animation will come to an end when max value is reached is dependent on the
+ * child animation's implementation.
+ *
+ * @param max maximum value of the property to be animated
+ * @return the Animation whose max value is being set
+ */
+ @SuppressWarnings("unchecked")
+ public T setMaxValue(float max) {
+ // This max value should be checked and handled in the subclass animations, instead of
+ // assuming the end of the animations when the max/min value is hit in the base class.
+ // The reason is that hitting max/min value may just be a transient state, such as during
+ // the spring oscillation.
+ mMaxValue = max;
+ return (T) this;
+ }
+
+ /**
+ * Sets the min value of the animation. Animations will not animate beyond their min value.
+ * Whether or not animation will come to an end when min value is reached is dependent on the
+ * child animation's implementation.
+ *
+ * @param min minimum value of the property to be animated
+ * @return the Animation whose min value is being set
+ */
+ @SuppressWarnings("unchecked")
+ public T setMinValue(float min) {
+ mMinValue = min;
+ return (T) this;
+ }
+
+ /**
+ * Adds an end listener to the animation for receiving onAnimationEnd callbacks. If the listener
+ * is {@code null} or has already been added to the list of listeners for the animation, no op.
+ *
+ * @param listener the listener to be added
+ * @return the animation to which the listener is added
+ */
+ @SuppressWarnings("unchecked")
+ public T addEndListener(OnAnimationEndListener listener) {
+ if (!mEndListeners.contains(listener)) {
+ mEndListeners.add(listener);
+ }
+ return (T) this;
+ }
+
+ /**
+ * Removes the end listener from the animation, so as to stop receiving animation end callbacks.
+ *
+ * @param listener the listener to be removed
+ */
+ public void removeEndListener(OnAnimationEndListener listener) {
+ removeEntry(mEndListeners, listener);
+ }
+
+ /**
+ * Adds an update listener to the animation for receiving per-frame animation update callbacks.
+ * If the listener is {@code null} or has already been added to the list of listeners for the
+ * animation, no op.
+ *
+ * <p>Note that update listener should only be added before the start of the animation.
+ *
+ * @param listener the listener to be added
+ * @return the animation to which the listener is added
+ * @throws UnsupportedOperationException if the update listener is added after the animation has
+ * started
+ */
+ @SuppressWarnings("unchecked")
+ public T addUpdateListener(OnAnimationUpdateListener listener) {
+ if (isRunning()) {
+ // Require update listener to be added before the animation, such as when we start
+ // the animation, we know whether the animation is RenderThread compatible.
+ throw new UnsupportedOperationException("Error: Update listeners must be added before"
+ + "the animation.");
+ }
+ if (!mUpdateListeners.contains(listener)) {
+ mUpdateListeners.add(listener);
+ }
+ return (T) this;
+ }
+
+ /**
+ * Removes the update listener from the animation, so as to stop receiving animation update
+ * callbacks.
+ *
+ * @param listener the listener to be removed
+ */
+ public void removeUpdateListener(OnAnimationUpdateListener listener) {
+ removeEntry(mUpdateListeners, listener);
+ }
+
+
+ /**
+ * This method sets the minimal change of animation value that is visible to users, which helps
+ * determine a reasonable threshold for the animation's termination condition. It is critical
+ * to set the minimal visible change for custom properties (i.e. non-<code>ViewProperty</code>s)
+ * unless the custom property is in pixels.
+ *
+ * <p>For custom properties, this minimum visible change defaults to change in pixel
+ * (i.e. {@link #MIN_VISIBLE_CHANGE_PIXELS}. It is recommended to adjust this value that is
+ * reasonable for the property to be animated. A general rule of thumb to calculate such a value
+ * is: minimum visible change = range of custom property value / corresponding pixel range. For
+ * example, if the property to be animated is a progress (from 0 to 100) that corresponds to a
+ * 200-pixel change. Then the min visible change should be 100 / 200. (i.e. 0.5).
+ *
+ * <p>It's not necessary to call this method when animating {@link ViewProperty}s, as the
+ * minimum visible change will be derived from the property. For example, if the property to be
+ * animated is in pixels (i.e. {@link #TRANSLATION_X}, {@link #TRANSLATION_Y},
+ * {@link #TRANSLATION_Z}, @{@link #SCROLL_X} or {@link #SCROLL_Y}), the default minimum visible
+ * change is 1 (pixel). For {@link #ROTATION}, {@link #ROTATION_X} or {@link #ROTATION_Y}, the
+ * animation will use {@link #MIN_VISIBLE_CHANGE_ROTATION_DEGREES} as the min visible change,
+ * which is 1/10. Similarly, the minimum visible change for alpha (
+ * i.e. {@link #MIN_VISIBLE_CHANGE_ALPHA} is defined as 1 / 256.
+ *
+ * @param minimumVisibleChange minimum change in property value that is visible to users
+ * @return the animation whose min visible change is being set
+ * @throws IllegalArgumentException if the given threshold is not positive
+ */
+ @SuppressWarnings("unchecked")
+ public T setMinimumVisibleChange(@FloatRange(from = 0.0, fromInclusive = false)
+ float minimumVisibleChange) {
+ if (minimumVisibleChange <= 0) {
+ throw new IllegalArgumentException("Minimum visible change must be positive.");
+ }
+ mMinVisibleChange = minimumVisibleChange;
+ setValueThreshold(minimumVisibleChange * THRESHOLD_MULTIPLIER);
+ return (T) this;
+ }
+
+ /**
+ * Returns the minimum change in the animation property that could be visibly different to
+ * users.
+ *
+ * @return minimum change in property value that is visible to users
+ */
+ public float getMinimumVisibleChange() {
+ return mMinVisibleChange;
+ }
+
+ /**
+ * Remove {@code null} entries from the list.
+ */
+ private static <T> void removeNullEntries(ArrayList<T> list) {
+ // Clean up null entries
+ for (int i = list.size() - 1; i >= 0; i--) {
+ if (list.get(i) == null) {
+ list.remove(i);
+ }
+ }
+ }
+
+ /**
+ * Remove an entry from the list by marking it {@code null} and clean up later.
+ */
+ private static <T> void removeEntry(ArrayList<T> list, T entry) {
+ int id = list.indexOf(entry);
+ if (id >= 0) {
+ list.set(id, null);
+ }
+ }
+
+ /****************Animation Lifecycle Management***************/
+
+ /**
+ * Starts an animation. If the animation has already been started, no op. Note that calling
+ * {@link #start()} will not immediately set the property value to start value of the animation.
+ * The property values will be changed at each animation pulse, which happens before the draw
+ * pass. As a result, the changes will be reflected in the next frame, the same as if the values
+ * were set immediately. This method should only be called on main thread.
+ *
+ * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler
+ * is created on the same thread as the first call to start/cancel an animation. All the
+ * subsequent animation lifecycle manipulations need to be on that same thread, until the
+ * AnimationHandler is reset (using [setAnimationHandler]).
+ *
+ * @throws AndroidRuntimeException if this method is not called on the same thread as the
+ * animation handler
+ */
+ @MainThread
+ public void start() {
+ if (!isCurrentThread()) {
+ throw new AndroidRuntimeException("Animations may only be started on the same thread "
+ + "as the animation handler");
+ }
+ if (!mRunning) {
+ startAnimationInternal();
+ }
+ }
+
+ boolean isCurrentThread() {
+ return Thread.currentThread() == Looper.myLooper().getThread();
+ }
+
+ /**
+ * Cancels the on-going animation. If the animation hasn't started, no op.
+ *
+ * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler
+ * is created on the same thread as the first call to start/cancel an animation. All the
+ * subsequent animation lifecycle manipulations need to be on that same thread, until the
+ * AnimationHandler is reset (using [setAnimationHandler]).
+ *
+ * @throws AndroidRuntimeException if this method is not called on the same thread as the
+ * animation handler
+ */
+ @MainThread
+ public void cancel() {
+ if (!isCurrentThread()) {
+ throw new AndroidRuntimeException("Animations may only be canceled from the same "
+ + "thread as the animation handler");
+ }
+ if (mRunning) {
+ endAnimationInternal(true);
+ }
+ }
+
+ /**
+ * Returns whether the animation is currently running.
+ *
+ * @return {@code true} if the animation is currently running, {@code false} otherwise
+ */
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ /************************** Private APIs below ********************************/
+
+ // This gets called when the animation is started, to finish the setup of the animation
+ // before the animation pulsing starts.
+ private void startAnimationInternal() {
+ if (!mRunning) {
+ mRunning = true;
+ if (!mStartValueIsSet) {
+ mValue = getPropertyValue();
+ }
+ // Sanity check:
+ if (mValue > mMaxValue || mValue < mMinValue) {
+ throw new IllegalArgumentException("Starting value need to be in between min"
+ + " value and max value");
+ }
+ getAnimationHandler().addAnimationFrameCallback(this, 0);
+ }
+ }
+
+ /**
+ * This gets call on each frame of the animation. Animation value and velocity are updated
+ * in this method based on the new frame time. The property value of the view being animated
+ * is then updated. The animation's ending conditions are also checked in this method. Once
+ * the animation reaches equilibrium, the animation will come to its end, and end listeners
+ * will be notified, if any.
+ */
+ @Override
+ public boolean doAnimationFrame(long frameTime) {
+ if (mLastFrameTime == 0) {
+ // First frame.
+ mLastFrameTime = frameTime;
+ setPropertyValue(mValue);
+ return false;
+ }
+ long deltaT = frameTime - mLastFrameTime;
+ mLastFrameTime = frameTime;
+ float durationScale = ValueAnimator.getDurationScale();
+ deltaT = durationScale == 0.0f ? Integer.MAX_VALUE : (long) (deltaT / durationScale);
+ boolean finished = updateValueAndVelocity(deltaT);
+ // Clamp value & velocity.
+ mValue = Math.min(mValue, mMaxValue);
+ mValue = Math.max(mValue, mMinValue);
+
+ setPropertyValue(mValue);
+
+ if (finished) {
+ endAnimationInternal(false);
+ }
+ return finished;
+ }
+
+ @Override
+ public void commitAnimationFrame(long frameTime) {
+ doAnimationFrame(frameTime);
+ }
+
+ /**
+ * Updates the animation state (i.e. value and velocity). This method is package private, so
+ * subclasses can override this method to calculate the new value and velocity in their custom
+ * way.
+ *
+ * @param deltaT time elapsed in millisecond since last frame
+ * @return whether the animation has finished
+ */
+ abstract boolean updateValueAndVelocity(long deltaT);
+
+ /**
+ * Internal method to reset the animation states when animation is finished/canceled.
+ */
+ private void endAnimationInternal(boolean canceled) {
+ mRunning = false;
+ getAnimationHandler().removeCallback(this);
+ mLastFrameTime = 0;
+ mStartValueIsSet = false;
+ for (int i = 0; i < mEndListeners.size(); i++) {
+ if (mEndListeners.get(i) != null) {
+ mEndListeners.get(i).onAnimationEnd(this, canceled, mValue, mVelocity);
+ }
+ }
+ removeNullEntries(mEndListeners);
+ }
+
+ /**
+ * Updates the property value through the corresponding setter.
+ */
+ @SuppressWarnings("unchecked")
+ void setPropertyValue(float value) {
+ mProperty.setValue(mTarget, value);
+ for (int i = 0; i < mUpdateListeners.size(); i++) {
+ if (mUpdateListeners.get(i) != null) {
+ mUpdateListeners.get(i).onAnimationUpdate(this, mValue, mVelocity);
+ }
+ }
+ removeNullEntries(mUpdateListeners);
+ }
+
+ /**
+ * Returns the default threshold.
+ */
+ float getValueThreshold() {
+ return mMinVisibleChange * THRESHOLD_MULTIPLIER;
+ }
+
+ /**
+ * Obtain the property value through the corresponding getter.
+ */
+ @SuppressWarnings("unchecked")
+ private float getPropertyValue() {
+ return (Float) mProperty.get(mTarget);
+ }
+
+ /**
+ * Returns the {@link AnimationHandler} used to schedule updates for this animator.
+ *
+ * @return the {@link AnimationHandler} for this animator.
+ */
+ @NonNull
+ public AnimationHandler getAnimationHandler() {
+ return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance();
+ }
+
+ /****************Sub class animations**************/
+ /**
+ * Returns the acceleration at the given value with the given velocity.
+ **/
+ abstract float getAcceleration(float value, float velocity);
+
+ /**
+ * Returns whether the animation has reached equilibrium.
+ */
+ abstract boolean isAtEquilibrium(float value, float velocity);
+
+ /**
+ * Updates the default value threshold for the animation based on the property to be animated.
+ */
+ abstract void setValueThreshold(float threshold);
+
+ /**
+ * An animation listener that receives end notifications from an animation.
+ */
+ public interface OnAnimationEndListener {
+ /**
+ * Notifies the end of an animation. Note that this callback will be invoked not only when
+ * an animation reach equilibrium, but also when the animation is canceled.
+ *
+ * @param animation animation that has ended or was canceled
+ * @param canceled whether the animation has been canceled
+ * @param value the final value when the animation stopped
+ * @param velocity the final velocity when the animation stopped
+ */
+ void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+ float velocity);
+ }
+
+ /**
+ * Implementors of this interface can add themselves as update listeners
+ * to an <code>DynamicAnimation</code> instance to receive callbacks on every animation
+ * frame, after the current frame's values have been calculated for that
+ * <code>DynamicAnimation</code>.
+ */
+ public interface OnAnimationUpdateListener {
+
+ /**
+ * Notifies the occurrence of another frame of the animation.
+ *
+ * @param animation animation that the update listener is added to
+ * @param value the current value of the animation
+ * @param velocity the current velocity of the animation
+ */
+ void onAnimationUpdate(DynamicAnimation animation, float value, float velocity);
+ }
+}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java b/core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java
new file mode 100644
index 000000000000..c3a2cacd16ec
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/FloatValueHolder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dynamicanimation.animation;
+
+/**
+ * <p>FloatValueHolder holds a float value. FloatValueHolder provides a setter and a getter (
+ * i.e. {@link #setValue(float)} and {@link #getValue()}) to access this float value. Animations can
+ * be performed on a FloatValueHolder instance. During each frame of the animation, the
+ * FloatValueHolder will have its value updated via {@link #setValue(float)}. The caller can
+ * obtain the up-to-date animation value via {@link FloatValueHolder#getValue()}.
+ *
+ * @see SpringAnimation#SpringAnimation(FloatValueHolder)
+ */
+
+public class FloatValueHolder {
+ private float mValue = 0.0f;
+
+ /**
+ * Constructs a holder for a float value that is initialized to 0.
+ */
+ public FloatValueHolder() {
+ }
+
+ /**
+ * Constructs a holder for a float value that is initialized to the input value.
+ *
+ * @param value the value to initialize the value held in the FloatValueHolder
+ */
+ public FloatValueHolder(float value) {
+ setValue(value);
+ }
+
+ /**
+ * Sets the value held in the FloatValueHolder instance.
+ *
+ * @param value float value held in the FloatValueHolder instance
+ */
+ public void setValue(float value) {
+ mValue = value;
+ }
+
+ /**
+ * Returns the float value held in the FloatValueHolder instance.
+ *
+ * @return float value held in the FloatValueHolder instance
+ */
+ public float getValue() {
+ return mValue;
+ }
+}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/Force.java b/core/java/com/android/internal/dynamicanimation/animation/Force.java
new file mode 100644
index 000000000000..fcb9c459fff3
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/Force.java
@@ -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.internal.dynamicanimation.animation;
+
+/**
+ * Hide this for now, in case we want to change the API.
+ */
+interface Force {
+ // Acceleration based on position.
+ float getAcceleration(float position, float velocity);
+
+ boolean isAtEquilibrium(float value, float velocity);
+}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java b/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java
new file mode 100644
index 000000000000..2f3b72c4f97d
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dynamicanimation.animation;
+
+import android.util.AndroidRuntimeException;
+import android.util.FloatProperty;
+
+/**
+ * SpringAnimation is an animation that is driven by a {@link SpringForce}. The spring force defines
+ * the spring's stiffness, damping ratio, as well as the rest position. Once the SpringAnimation is
+ * started, on each frame the spring force will update the animation's value and velocity.
+ * The animation will continue to run until the spring force reaches equilibrium. If the spring used
+ * in the animation is undamped, the animation will never reach equilibrium. Instead, it will
+ * oscillate forever.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * </div>
+ *
+ * <p>To create a simple {@link SpringAnimation} that uses the default {@link SpringForce}:</p>
+ * <pre class="prettyprint">
+ * // Create an animation to animate view's X property, set the rest position of the
+ * // default spring to 0, and start the animation with a starting velocity of 5000 (pixel/s).
+ * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.X, 0)
+ * .setStartVelocity(5000);
+ * anim.start();
+ * </pre>
+ *
+ * <p>Alternatively, a {@link SpringAnimation} can take a pre-configured {@link SpringForce}, and
+ * use that to drive the animation. </p>
+ * <pre class="prettyprint">
+ * // Create a low stiffness, low bounce spring at position 0.
+ * SpringForce spring = new SpringForce(0)
+ * .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ * .setStiffness(SpringForce.STIFFNESS_LOW);
+ * // Create an animation to animate view's scaleY property, and start the animation using
+ * // the spring above and a starting value of 0.5. Additionally, constrain the range of value for
+ * // the animation to be non-negative, effectively preventing any spring overshoot.
+ * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.SCALE_Y)
+ * .setMinValue(0).setSpring(spring).setStartValue(1);
+ * anim.start();
+ * </pre>
+ */
+public final class SpringAnimation extends DynamicAnimation<SpringAnimation> {
+
+ private SpringForce mSpring = null;
+ private float mPendingPosition = UNSET;
+ private static final float UNSET = Float.MAX_VALUE;
+ private boolean mEndRequested = false;
+
+ /**
+ * <p>This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During
+ * the animation, the {@link FloatValueHolder} instance will be updated via
+ * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date
+ * animation value via {@link FloatValueHolder#getValue()}.
+ *
+ * <p><strong>Note:</strong> changing the value in the {@link FloatValueHolder} via
+ * {@link FloatValueHolder#setValue(float)} outside of the animation during an
+ * animation run will not have any effect on the on-going animation.
+ *
+ * @param floatValueHolder the property to be animated
+ */
+ public SpringAnimation(FloatValueHolder floatValueHolder) {
+ super(floatValueHolder);
+ }
+
+ /**
+ * <p>This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During
+ * the animation, the {@link FloatValueHolder} instance will be updated via
+ * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date
+ * animation value via {@link FloatValueHolder#getValue()}.
+ *
+ * A Spring will be created with the given final position and default stiffness and damping
+ * ratio. This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}.
+ *
+ * <p><strong>Note:</strong> changing the value in the {@link FloatValueHolder} via
+ * {@link FloatValueHolder#setValue(float)} outside of the animation during an
+ * animation run will not have any effect on the on-going animation.
+ *
+ * @param floatValueHolder the property to be animated
+ * @param finalPosition the final position of the spring to be created.
+ */
+ public SpringAnimation(FloatValueHolder floatValueHolder, float finalPosition) {
+ super(floatValueHolder);
+ mSpring = new SpringForce(finalPosition);
+ }
+
+ /**
+ * This creates a SpringAnimation that animates the property of the given object.
+ * Note, a spring will need to setup through {@link #setSpring(SpringForce)} before
+ * the animation starts.
+ *
+ * @param object the Object whose property will be animated
+ * @param property the property to be animated
+ * @param <K> the class on which the Property is declared
+ */
+ public <K> SpringAnimation(K object, FloatProperty<K> property) {
+ super(object, property);
+ }
+
+ /**
+ * This creates a SpringAnimation that animates the property of the given object. A Spring will
+ * be created with the given final position and default stiffness and damping ratio.
+ * This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}.
+ *
+ * @param object the Object whose property will be animated
+ * @param property the property to be animated
+ * @param finalPosition the final position of the spring to be created.
+ * @param <K> the class on which the Property is declared
+ */
+ public <K> SpringAnimation(K object, FloatProperty<K> property,
+ float finalPosition) {
+ super(object, property);
+ mSpring = new SpringForce(finalPosition);
+ }
+
+ /**
+ * Returns the spring that the animation uses for animations.
+ *
+ * @return the spring that the animation uses for animations
+ */
+ public SpringForce getSpring() {
+ return mSpring;
+ }
+
+ /**
+ * Uses the given spring as the force that drives this animation. If this spring force has its
+ * parameters re-configured during the animation, the new configuration will be reflected in the
+ * animation immediately.
+ *
+ * @param force a pre-defined spring force that drives the animation
+ * @return the animation that the spring force is set on
+ */
+ public SpringAnimation setSpring(SpringForce force) {
+ mSpring = force;
+ return this;
+ }
+
+ @Override
+ public void start() {
+ sanityCheck();
+ mSpring.setValueThreshold(getValueThreshold());
+ super.start();
+ }
+
+ /**
+ * Updates the final position of the spring.
+ * <p/>
+ * When the animation is running, calling this method would assume the position change of the
+ * spring as a continuous movement since last frame, which yields more accurate results than
+ * changing the spring position directly through {@link SpringForce#setFinalPosition(float)}.
+ * <p/>
+ * If the animation hasn't started, calling this method will change the spring position, and
+ * immediately start the animation.
+ *
+ * @param finalPosition rest position of the spring
+ */
+ public void animateToFinalPosition(float finalPosition) {
+ if (isRunning()) {
+ mPendingPosition = finalPosition;
+ } else {
+ if (mSpring == null) {
+ mSpring = new SpringForce(finalPosition);
+ }
+ mSpring.setFinalPosition(finalPosition);
+ start();
+ }
+ }
+
+ /**
+ * Cancels the on-going animation. If the animation hasn't started, no op. Note that this method
+ * should only be called on main thread.
+ *
+ * @throws AndroidRuntimeException if this method is not called on the main thread
+ */
+ @Override
+ public void cancel() {
+ super.cancel();
+ if (mPendingPosition != UNSET) {
+ if (mSpring == null) {
+ mSpring = new SpringForce(mPendingPosition);
+ } else {
+ mSpring.setFinalPosition(mPendingPosition);
+ }
+ mPendingPosition = UNSET;
+ }
+ }
+
+ /**
+ * Skips to the end of the animation. If the spring is undamped, an
+ * {@link IllegalStateException} will be thrown, as the animation would never reach to an end.
+ * It is recommended to check {@link #canSkipToEnd()} before calling this method. If animation
+ * is not running, no-op.
+ *
+ * Unless a AnimationHandler is provided via setAnimationHandler, a default AnimationHandler
+ * is created on the same thread as the first call to start/cancel an animation. All the
+ * subsequent animation lifecycle manipulations need to be on that same thread, until the
+ * AnimationHandler is reset (using [setAnimationHandler]).
+ *
+ * @throws IllegalStateException if the spring is undamped (i.e. damping ratio = 0)
+ * @throws AndroidRuntimeException if this method is not called on the same thread as the
+ * animation handler
+ */
+ public void skipToEnd() {
+ if (!canSkipToEnd()) {
+ throw new UnsupportedOperationException("Spring animations can only come to an end"
+ + " when there is damping");
+ }
+ if (!isCurrentThread()) {
+ throw new AndroidRuntimeException("Animations may only be started on the same thread "
+ + "as the animation handler");
+ }
+ if (mRunning) {
+ mEndRequested = true;
+ }
+ }
+
+ /**
+ * Queries whether the spring can eventually come to the rest position.
+ *
+ * @return {@code true} if the spring is damped, otherwise {@code false}
+ */
+ public boolean canSkipToEnd() {
+ return mSpring.mDampingRatio > 0;
+ }
+
+ /************************ Below are private APIs *************************/
+
+ private void sanityCheck() {
+ if (mSpring == null) {
+ throw new UnsupportedOperationException("Incomplete SpringAnimation: Either final"
+ + " position or a spring force needs to be set.");
+ }
+ double finalPosition = mSpring.getFinalPosition();
+ if (finalPosition > mMaxValue) {
+ throw new UnsupportedOperationException("Final position of the spring cannot be greater"
+ + " than the max value.");
+ } else if (finalPosition < mMinValue) {
+ throw new UnsupportedOperationException("Final position of the spring cannot be less"
+ + " than the min value.");
+ }
+ }
+
+ @Override
+ boolean updateValueAndVelocity(long deltaT) {
+ // If user had requested end, then update the value and velocity to end state and consider
+ // animation done.
+ if (mEndRequested) {
+ if (mPendingPosition != UNSET) {
+ mSpring.setFinalPosition(mPendingPosition);
+ mPendingPosition = UNSET;
+ }
+ mValue = mSpring.getFinalPosition();
+ mVelocity = 0;
+ mEndRequested = false;
+ return true;
+ }
+
+ if (mPendingPosition != UNSET) {
+ // Approximate by considering half of the time spring position stayed at the old
+ // position, half of the time it's at the new position.
+ MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT / 2);
+ mSpring.setFinalPosition(mPendingPosition);
+ mPendingPosition = UNSET;
+
+ massState = mSpring.updateValues(massState.mValue, massState.mVelocity, deltaT / 2);
+ mValue = massState.mValue;
+ mVelocity = massState.mVelocity;
+
+ } else {
+ MassState massState = mSpring.updateValues(mValue, mVelocity, deltaT);
+ mValue = massState.mValue;
+ mVelocity = massState.mVelocity;
+ }
+
+ mValue = Math.max(mValue, mMinValue);
+ mValue = Math.min(mValue, mMaxValue);
+
+ if (isAtEquilibrium(mValue, mVelocity)) {
+ mValue = mSpring.getFinalPosition();
+ mVelocity = 0f;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ float getAcceleration(float value, float velocity) {
+ return mSpring.getAcceleration(value, velocity);
+ }
+
+ @Override
+ boolean isAtEquilibrium(float value, float velocity) {
+ return mSpring.isAtEquilibrium(value, velocity);
+ }
+
+ @Override
+ void setValueThreshold(float threshold) {
+ }
+}
diff --git a/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java b/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java
new file mode 100644
index 000000000000..36242ae2cf3d
--- /dev/null
+++ b/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dynamicanimation.animation;
+
+import android.annotation.FloatRange;
+
+/**
+ * Spring Force defines the characteristics of the spring being used in the animation.
+ * <p>
+ * By configuring the stiffness and damping ratio, callers can create a spring with the look and
+ * feel suits their use case. Stiffness corresponds to the spring constant. The stiffer the spring
+ * is, the harder it is to stretch it, the faster it undergoes dampening.
+ * <p>
+ * Spring damping ratio describes how oscillations in a system decay after a disturbance.
+ * When damping ratio > 1* (i.e. over-damped), the object will quickly return to the rest position
+ * without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will
+ * return to equilibrium within the shortest amount of time. When damping ratio is less than 1
+ * (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without any
+ * damping (i.e. damping ratio = 0), the mass will oscillate forever.
+ */
+public final class SpringForce implements Force {
+ /**
+ * Stiffness constant for extremely stiff spring.
+ */
+ public static final float STIFFNESS_HIGH = 10_000f;
+ /**
+ * Stiffness constant for medium stiff spring. This is the default stiffness for spring force.
+ */
+ public static final float STIFFNESS_MEDIUM = 1500f;
+ /**
+ * Stiffness constant for a spring with low stiffness.
+ */
+ public static final float STIFFNESS_LOW = 200f;
+ /**
+ * Stiffness constant for a spring with very low stiffness.
+ */
+ public static final float STIFFNESS_VERY_LOW = 50f;
+
+ /**
+ * Damping ratio for a very bouncy spring. Note for under-damped springs
+ * (i.e. damping ratio < 1), the lower the damping ratio, the more bouncy the spring.
+ */
+ public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2f;
+ /**
+ * Damping ratio for a medium bouncy spring. This is also the default damping ratio for spring
+ * force. Note for under-damped springs (i.e. damping ratio < 1), the lower the damping ratio,
+ * the more bouncy the spring.
+ */
+ public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5f;
+ /**
+ * Damping ratio for a spring with low bounciness. Note for under-damped springs
+ * (i.e. damping ratio < 1), the lower the damping ratio, the higher the bounciness.
+ */
+ public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75f;
+ /**
+ * Damping ratio for a spring with no bounciness. This damping ratio will create a critically
+ * damped spring that returns to equilibrium within the shortest amount of time without
+ * oscillating.
+ */
+ public static final float DAMPING_RATIO_NO_BOUNCY = 1f;
+
+ // This multiplier is used to calculate the velocity threshold given a certain value threshold.
+ // The idea is that if it takes >= 1 frame to move the value threshold amount, then the velocity
+ // is a reasonable threshold.
+ private static final double VELOCITY_THRESHOLD_MULTIPLIER = 1000.0 / 16.0;
+
+ // Natural frequency
+ double mNaturalFreq = Math.sqrt(STIFFNESS_MEDIUM);
+ // Damping ratio.
+ double mDampingRatio = DAMPING_RATIO_MEDIUM_BOUNCY;
+
+ // Value to indicate an unset state.
+ private static final double UNSET = Double.MAX_VALUE;
+
+ // Indicates whether the spring has been initialized
+ private boolean mInitialized = false;
+
+ // Threshold for velocity and value to determine when it's reasonable to assume that the spring
+ // is approximately at rest.
+ private double mValueThreshold;
+ private double mVelocityThreshold;
+
+ // Intermediate values to simplify the spring function calculation per frame.
+ private double mGammaPlus;
+ private double mGammaMinus;
+ private double mDampedFreq;
+
+ // Final position of the spring. This must be set before the start of the animation.
+ private double mFinalPosition = UNSET;
+
+ // Internal state to hold a value/velocity pair.
+ private final DynamicAnimation.MassState mMassState = new DynamicAnimation.MassState();
+
+ /**
+ * Creates a spring force. Note that final position of the spring must be set through
+ * {@link #setFinalPosition(float)} before the spring animation starts.
+ */
+ public SpringForce() {
+ // No op.
+ }
+
+ /**
+ * Creates a spring with a given final rest position.
+ *
+ * @param finalPosition final position of the spring when it reaches equilibrium
+ */
+ public SpringForce(float finalPosition) {
+ mFinalPosition = finalPosition;
+ }
+
+ /**
+ * Sets the stiffness of a spring. The more stiff a spring is, the more force it applies to
+ * the object attached when the spring is not at the final position. Default stiffness is
+ * {@link #STIFFNESS_MEDIUM}.
+ *
+ * @param stiffness non-negative stiffness constant of a spring
+ * @return the spring force that the given stiffness is set on
+ * @throws IllegalArgumentException if the given spring stiffness is not positive
+ */
+ public SpringForce setStiffness(
+ @FloatRange(from = 0.0, fromInclusive = false) float stiffness) {
+ if (stiffness <= 0) {
+ throw new IllegalArgumentException("Spring stiffness constant must be positive.");
+ }
+ mNaturalFreq = Math.sqrt(stiffness);
+ // All the intermediate values need to be recalculated.
+ mInitialized = false;
+ return this;
+ }
+
+ /**
+ * Gets the stiffness of the spring.
+ *
+ * @return the stiffness of the spring
+ */
+ public float getStiffness() {
+ return (float) (mNaturalFreq * mNaturalFreq);
+ }
+
+ /**
+ * Spring damping ratio describes how oscillations in a system decay after a disturbance.
+ * <p>
+ * When damping ratio > 1 (over-damped), the object will quickly return to the rest position
+ * without overshooting. If damping ratio equals to 1 (i.e. critically damped), the object will
+ * return to equilibrium within the shortest amount of time. When damping ratio is less than 1
+ * (i.e. under-damped), the mass tends to overshoot, and return, and overshoot again. Without
+ * any damping (i.e. damping ratio = 0), the mass will oscillate forever.
+ * <p>
+ * Default damping ratio is {@link #DAMPING_RATIO_MEDIUM_BOUNCY}.
+ *
+ * @param dampingRatio damping ratio of the spring, it should be non-negative
+ * @return the spring force that the given damping ratio is set on
+ * @throws IllegalArgumentException if the {@param dampingRatio} is negative.
+ */
+ public SpringForce setDampingRatio(@FloatRange(from = 0.0) float dampingRatio) {
+ if (dampingRatio < 0) {
+ throw new IllegalArgumentException("Damping ratio must be non-negative");
+ }
+ mDampingRatio = dampingRatio;
+ // All the intermediate values need to be recalculated.
+ mInitialized = false;
+ return this;
+ }
+
+ /**
+ * Returns the damping ratio of the spring.
+ *
+ * @return damping ratio of the spring
+ */
+ public float getDampingRatio() {
+ return (float) mDampingRatio;
+ }
+
+ /**
+ * Sets the rest position of the spring.
+ *
+ * @param finalPosition rest position of the spring
+ * @return the spring force that the given final position is set on
+ */
+ public SpringForce setFinalPosition(float finalPosition) {
+ mFinalPosition = finalPosition;
+ return this;
+ }
+
+ /**
+ * Returns the rest position of the spring.
+ *
+ * @return rest position of the spring
+ */
+ public float getFinalPosition() {
+ return (float) mFinalPosition;
+ }
+
+ /*********************** Below are private APIs *********************/
+
+ @Override
+ public float getAcceleration(float lastDisplacement, float lastVelocity) {
+
+ lastDisplacement -= getFinalPosition();
+
+ double k = mNaturalFreq * mNaturalFreq;
+ double c = 2 * mNaturalFreq * mDampingRatio;
+
+ return (float) (-k * lastDisplacement - c * lastVelocity);
+ }
+
+ @Override
+ public boolean isAtEquilibrium(float value, float velocity) {
+ if (Math.abs(velocity) < mVelocityThreshold
+ && Math.abs(value - getFinalPosition()) < mValueThreshold) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Initialize the string by doing the necessary pre-calculation as well as some sanity check
+ * on the setup.
+ *
+ * @throws IllegalStateException if the final position is not yet set by the time the spring
+ * animation has started
+ */
+ private void init() {
+ if (mInitialized) {
+ return;
+ }
+
+ if (mFinalPosition == UNSET) {
+ throw new IllegalStateException("Error: Final position of the spring must be"
+ + " set before the animation starts");
+ }
+
+ if (mDampingRatio > 1) {
+ // Over damping
+ mGammaPlus = -mDampingRatio * mNaturalFreq
+ + mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1);
+ mGammaMinus = -mDampingRatio * mNaturalFreq
+ - mNaturalFreq * Math.sqrt(mDampingRatio * mDampingRatio - 1);
+ } else if (mDampingRatio >= 0 && mDampingRatio < 1) {
+ // Under damping
+ mDampedFreq = mNaturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
+ }
+
+ mInitialized = true;
+ }
+
+ /**
+ * Internal only call for Spring to calculate the spring position/velocity using
+ * an analytical approach.
+ */
+ DynamicAnimation.MassState updateValues(double lastDisplacement, double lastVelocity,
+ long timeElapsed) {
+ init();
+
+ double deltaT = timeElapsed / 1000d; // unit: seconds
+ lastDisplacement -= mFinalPosition;
+ double displacement;
+ double currentVelocity;
+ if (mDampingRatio > 1) {
+ // Overdamped
+ double coeffA = lastDisplacement - (mGammaMinus * lastDisplacement - lastVelocity)
+ / (mGammaMinus - mGammaPlus);
+ double coeffB = (mGammaMinus * lastDisplacement - lastVelocity)
+ / (mGammaMinus - mGammaPlus);
+ displacement = coeffA * Math.pow(Math.E, mGammaMinus * deltaT)
+ + coeffB * Math.pow(Math.E, mGammaPlus * deltaT);
+ currentVelocity = coeffA * mGammaMinus * Math.pow(Math.E, mGammaMinus * deltaT)
+ + coeffB * mGammaPlus * Math.pow(Math.E, mGammaPlus * deltaT);
+ } else if (mDampingRatio == 1) {
+ // Critically damped
+ double coeffA = lastDisplacement;
+ double coeffB = lastVelocity + mNaturalFreq * lastDisplacement;
+ displacement = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT);
+ currentVelocity = (coeffA + coeffB * deltaT) * Math.pow(Math.E, -mNaturalFreq * deltaT)
+ * -mNaturalFreq + coeffB * Math.pow(Math.E, -mNaturalFreq * deltaT);
+ } else {
+ // Underdamped
+ double cosCoeff = lastDisplacement;
+ double sinCoeff = (1 / mDampedFreq) * (mDampingRatio * mNaturalFreq
+ * lastDisplacement + lastVelocity);
+ displacement = Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
+ * (cosCoeff * Math.cos(mDampedFreq * deltaT)
+ + sinCoeff * Math.sin(mDampedFreq * deltaT));
+ currentVelocity = displacement * -mNaturalFreq * mDampingRatio
+ + Math.pow(Math.E, -mDampingRatio * mNaturalFreq * deltaT)
+ * (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT)
+ + mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
+ }
+
+ mMassState.mValue = (float) (displacement + mFinalPosition);
+ mMassState.mVelocity = (float) currentVelocity;
+ return mMassState;
+ }
+
+ /**
+ * This threshold defines how close the animation value needs to be before the animation can
+ * finish. This default value is based on the property being animated, e.g. animations on alpha,
+ * scale, translation or rotation would have different thresholds. This value should be small
+ * enough to avoid visual glitch of "jumping to the end". But it shouldn't be so small that
+ * animations take seconds to finish.
+ *
+ * @param threshold the difference between the animation value and final spring position that
+ * is allowed to end the animation when velocity is very low
+ */
+ void setValueThreshold(double threshold) {
+ mValueThreshold = Math.abs(threshold);
+ mVelocityThreshold = mValueThreshold * VELOCITY_THRESHOLD_MULTIPLIER;
+ }
+}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 5de72409133d..a05062b43d60 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -45,6 +45,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
@@ -223,6 +224,7 @@ public class InteractionJankMonitor {
public static final int CUJ_SHADE_CLEAR_ALL = 62;
public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63;
public static final int CUJ_LOCKSCREEN_OCCLUSION = 64;
+ public static final int CUJ_RECENTS_SCROLLING = 65;
private static final int NO_STATSD_LOGGING = -1;
@@ -296,6 +298,7 @@ public class InteractionJankMonitor {
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING,
};
private static volatile InteractionJankMonitor sInstance;
@@ -380,7 +383,8 @@ public class InteractionJankMonitor {
CUJ_TASKBAR_COLLAPSE,
CUJ_SHADE_CLEAR_ALL,
CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
- CUJ_LOCKSCREEN_OCCLUSION
+ CUJ_LOCKSCREEN_OCCLUSION,
+ CUJ_RECENTS_SCROLLING
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -893,6 +897,8 @@ public class InteractionJankMonitor {
return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION";
case CUJ_LOCKSCREEN_OCCLUSION:
return "LOCKSCREEN_OCCLUSION";
+ case CUJ_RECENTS_SCROLLING:
+ return "RECENTS_SCROLLING";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 8b96597767d3..6fed26c4a81d 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -1326,6 +1326,13 @@ public class BatteryStatsImpl extends BatteryStats {
LongSamplingCounter mMobileRadioActiveUnknownTime;
LongSamplingCounter mMobileRadioActiveUnknownCount;
+ /**
+ * The soonest the Mobile Radio stats can be updated due to a mobile radio power state change
+ * after it was last updated.
+ */
+ @VisibleForTesting
+ protected static final long MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS = 1000 * 60 * 10;
+
int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
@GuardedBy("this")
@@ -6260,6 +6267,15 @@ public class BatteryStatsImpl extends BatteryStats {
} else {
mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs);
mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs);
+
+ if (mLastModemActivityInfo != null) {
+ if (elapsedRealtimeMs < mLastModemActivityInfo.getTimestampMillis()
+ + MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS) {
+ // Modem Activity info has been collected recently, don't bother
+ // triggering another update.
+ return false;
+ }
+ }
// Tell the caller to collect radio network/power stats.
return true;
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 44cfe1aa4a79..1d4b246de5c8 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -322,4 +322,7 @@ oneway interface IStatusBar
/** Unregisters a nearby media devices provider. */
void unregisterNearbyMediaDevicesProvider(in INearbyMediaDevicesProvider provider);
+
+ /** Dump protos from SystemUI. The proto definition is defined there */
+ void dumpProto(in String[] args, in ParcelFileDescriptor pfd);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 964fe2d57b0d..f7467b5a9f86 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1147,7 +1147,28 @@
android:protectionLevel="dangerous" />
<!-- Allows an application to write to external storage.
- <p class="note"><strong>Note:</strong> If <em>both</em> your <a
+ <p><strong>Note: </strong>If your app targets {@link android.os.Build.VERSION_CODES#R} or
+ higher, this permission has no effect.
+
+ <p>If your app is on a device that runs API level 19 or higher, you don't need to declare
+ this permission to read and write files in your application-specific directories returned
+ by {@link android.content.Context#getExternalFilesDir} and
+ {@link android.content.Context#getExternalCacheDir}.
+
+ <p>Learn more about how to
+ <a href="{@docRoot}training/data-storage/shared/media#update-other-apps-files">modify media
+ files</a> that your app doesn't own, and how to
+ <a href="{@docRoot}training/data-storage/shared/documents-files">modify non-media files</a>
+ that your app doesn't own.
+
+ <p>If your app is a file manager and needs broad access to external storage files, then
+ the system must place your app on an allowlist so that you can successfully request the
+ <a href="#MANAGE_EXTERNAL_STORAGE><code>MANAGE_EXTERNAL_STORAGE</code></a> permission.
+ Learn more about the appropriate use cases for
+ <a href="{@docRoot}training/data-storage/manage-all-files>managing all files on a storage
+ device</a>.
+
+ <p>If <em>both</em> your <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
minSdkVersion}</a> and <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
@@ -1155,12 +1176,6 @@
grants your app this permission. If you don't need this permission, be sure your <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
targetSdkVersion}</a> is 4 or higher.
- <p>Starting in API level 19, this permission is <em>not</em> required to
- read/write files in your application-specific directories returned by
- {@link android.content.Context#getExternalFilesDir} and
- {@link android.content.Context#getExternalCacheDir}.
- <p>If this permission is not allowlisted for an app that targets an API level before
- {@link android.os.Build.VERSION_CODES#Q} this permission cannot be granted to apps.</p>
<p>Protection level: dangerous</p>
-->
<permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
diff --git a/core/res/res/anim/dream_activity_close_exit.xml b/core/res/res/anim/dream_activity_close_exit.xml
index c4599dad31a0..8df624fdd2e5 100644
--- a/core/res/res/anim/dream_activity_close_exit.xml
+++ b/core/res/res/anim/dream_activity_close_exit.xml
@@ -19,5 +19,5 @@
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="1.0"
android:toAlpha="0.0"
- android:duration="100" />
+ android:duration="@integer/config_dreamCloseAnimationDuration" />
diff --git a/core/res/res/anim/dream_activity_open_enter.xml b/core/res/res/anim/dream_activity_open_enter.xml
index 9e1c6e2ee0d7..d6d9c5c990f8 100644
--- a/core/res/res/anim/dream_activity_open_enter.xml
+++ b/core/res/res/anim/dream_activity_open_enter.xml
@@ -22,5 +22,5 @@ those two has to be the same. -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="0.0"
android:toAlpha="1.0"
- android:duration="1000" />
+ android:duration="@integer/config_dreamOpenAnimationDuration" />
diff --git a/core/res/res/anim/dream_activity_open_exit.xml b/core/res/res/anim/dream_activity_open_exit.xml
index 740f52856b7f..2c2e501eda69 100644
--- a/core/res/res/anim/dream_activity_open_exit.xml
+++ b/core/res/res/anim/dream_activity_open_exit.xml
@@ -22,4 +22,4 @@ dream_activity_open_enter animation. -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="1.0"
android:toAlpha="1.0"
- android:duration="1000" />
+ android:duration="@integer/config_dreamOpenAnimationDuration" />
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index a7f2aa7cba69..be1c939f0ff8 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -24,6 +24,7 @@
android:gravity="center_vertical"
android:orientation="horizontal"
android:theme="@style/Theme.DeviceDefault.Notification"
+ android:importantForAccessibility="no"
>
<ImageView
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 7c439008622e..46f0d81dc2ac 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -178,7 +178,7 @@
<string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"በጣም ብዙ <xliff:g id="CONTENT_TYPE">%s</xliff:g> ለመሰረዝ ተሞክሯል።"</string>
<string name="low_memory" product="tablet" msgid="5557552311566179924">"የጡባዊ ተኮ ማከማቻ ሙሉ ነው! ቦታ ነፃ ለማድረግ አንዳንድ ፋይሎች ሰርዝ።"</string>
<string name="low_memory" product="watch" msgid="3479447988234030194">"የእጅ ሰዓት ማከማቻ ሙሉ ነው። ቦታ ለማስለቀቅ አንዳንድ ፋይሎችን ይሰርዙ።"</string>
- <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV መሣሪያ ማከማቻ ሙሉ ነው። ባዶ ቦታን ነጻ ለማድረግ አንዳንድ ፋይሎችን ይሰርዙ።"</string>
+ <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV መሣሪያ ማከማቻ ሙሉ ነው። ባዶ ቦታን ነፃ ለማድረግ አንዳንድ ፋይሎችን ይሰርዙ።"</string>
<string name="low_memory" product="default" msgid="2539532364144025569">"የስልክ ማከማቻ ሙሉ ነው! ቦታ ነፃ ለማድረግ አንዳንድ ፋይሎች ሰርዝ።"</string>
<string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{የእውቅና ማረጋገጫ ባለስልጣን ተጭኗል}one{የእውቅና ማረጋገጫ ባለስልጣናት ተጭነዋል}other{የእውቅና ማረጋገጫ ባለስልጣናት ተጭነዋል}}"</string>
<string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"ባልታወቀ ሶስተኛ ወገን"</string>
@@ -1156,7 +1156,7 @@
<string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"የግቤት ስልትን ቀይር"</string>
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"የማከማቻ ቦታ እያለቀ ነው"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"አንዳንድ የስርዓት ተግባራት ላይሰሩ ይችላሉ"</string>
- <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ለስርዓቱ የሚሆን በቂ ቦታ የለም። 250 ሜባ ነጻ ቦታ እንዳለዎት ያረጋግጡና ዳግም ያስጀምሩ።"</string>
+ <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ለስርዓቱ የሚሆን በቂ ቦታ የለም። 250 ሜባ ነፃ ቦታ እንዳለዎት ያረጋግጡና ዳግም ያስጀምሩ።"</string>
<string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> እያሄደ ነው"</string>
<string name="app_running_notification_text" msgid="5120815883400228566">"ተጨማሪ መረጃ ለማግኘት ወይም መተግበሪያውን ለማቆም መታ ያድርጉ።"</string>
<string name="ok" msgid="2646370155170753815">"እሺ"</string>
@@ -1211,7 +1211,7 @@
<string name="aerr_restart" msgid="2789618625210505419">"መተግበሪያውን እንደገና ክፈት"</string>
<string name="aerr_report" msgid="3095644466849299308">"ግብረመልስ ይላኩ"</string>
<string name="aerr_close" msgid="3398336821267021852">"ዝጋ"</string>
- <string name="aerr_mute" msgid="2304972923480211376">"መሣሪያ ዳግም እስኪጀመር ድረስ ድምጽ ያጥፉ"</string>
+ <string name="aerr_mute" msgid="2304972923480211376">"መሣሪያ ዳግም እስኪጀመር ድረስ ድምፅ ያጥፉ"</string>
<string name="aerr_wait" msgid="3198677780474548217">"ጠብቅ"</string>
<string name="aerr_close_app" msgid="8318883106083050970">"መተግበሪያን ዝጋ"</string>
<string name="anr_title" msgid="7290329487067300120"></string>
@@ -1273,7 +1273,7 @@
<string name="dump_heap_ready_text" msgid="5849618132123045516">"የ<xliff:g id="PROC">%1$s</xliff:g> ሂደት ተራጋፊ ክምር ለማጋራት ለእርስዎ ይገኛል። ይጠንቀቁ፦ ይህ ተራጋፊ ክምር ሂደቱ ሊደርስባቸው የሚችለው ማንኛውም የግል መረጃ ሊኖረው ይችላል፣ ይህ እርስዎ የተየቧቸውን ነገሮች ሊያካትት ይችላል።"</string>
<string name="sendText" msgid="493003724401350724">"ለፅሁፍ ድርጊት ምረጥ"</string>
<string name="volume_ringtone" msgid="134784084629229029">"የስልክ ጥሪ ድምፅ"</string>
- <string name="volume_music" msgid="7727274216734955095">"የማህደረ መረጃ ድምጽ መጠን"</string>
+ <string name="volume_music" msgid="7727274216734955095">"የማህደረ መረጃ ድምፅ መጠን"</string>
<string name="volume_music_hint_playing_through_bluetooth" msgid="2614142915948898228">"በብሉቱዝ በኩል ማጫወት"</string>
<string name="volume_music_hint_silent_ringtone_selected" msgid="1514829655029062233">"የፀጥታ የስልክ የደውል ድምፅ ተዘጋጅቷል"</string>
<string name="volume_call" msgid="7625321655265747433">"የጥሪ ላይ ድም ፅ መጨመሪያ/መቀነሻ"</string>
@@ -1284,7 +1284,7 @@
<string name="volume_icon_description_bluetooth" msgid="7540388479345558400">"የብሉቱዝ ድምፅ መጠን"</string>
<string name="volume_icon_description_ringer" msgid="2187800636867423459">"የስልክ ጥሪ ድምፅ መጠን"</string>
<string name="volume_icon_description_incall" msgid="4491255105381227919">"የስልክ ጥሪ ድምፅ መጠን"</string>
- <string name="volume_icon_description_media" msgid="4997633254078171233">"የማህደረ መረጃ ድምጽ መጠን"</string>
+ <string name="volume_icon_description_media" msgid="4997633254078171233">"የማህደረ መረጃ ድምፅ መጠን"</string>
<string name="volume_icon_description_notification" msgid="579091344110747279">"የማሳወቂያ ክፍልፍል"</string>
<string name="ringtone_default" msgid="9118299121288174597">"ነባሪ የስልክ ላይ ጥሪ"</string>
<string name="ringtone_default_with_actual" msgid="2709686194556159773">"ነባሪ (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
@@ -1617,7 +1617,7 @@
<string name="default_audio_route_name_headphones" msgid="6954070994792640762">"የጆሮ ማዳመጫዎች"</string>
<string name="default_audio_route_name_usb" msgid="895668743163316932">"ዩ ኤስ ቢ"</string>
<string name="default_audio_route_category_name" msgid="5241740395748134483">"ስርዓት"</string>
- <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"የብሉቱዝ ድምጽ"</string>
+ <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"የብሉቱዝ ድምፅ"</string>
<string name="wireless_display_route_description" msgid="8297563323032966831">"ገመድ አልባ ማሳያ"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"Cast"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"ከመሳሪያ ጋር ያገናኙ"</string>
@@ -1674,7 +1674,7 @@
<string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"የመክፈቻ ስርዓተ ጥለቱን <xliff:g id="NUMBER_0">%1$d</xliff:g> ጊዜ በትክክል አልሳሉትም። ከ<xliff:g id="NUMBER_1">%2$d</xliff:g> ተጨማሪ ያልተሳኩ ሙከራዎች በኋላ የኢሜይል መለያ ተጠቅመው ስልክዎን እንዲከፍቱ ይጠየቃሉ።\n\nእባክዎ ከ<xliff:g id="NUMBER_2">%3$d</xliff:g> ሰከንዶች በኋላ እንደገና ይሞክሩ።"</string>
<string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
<string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"አስወግድ"</string>
- <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ድምጹ ከሚመከረው መጠን በላይ ከፍ ይበል?\n\nበከፍተኛ ድምጽ ለረጅም ጊዜ ማዳመጥ ጆሮዎን ሊጎዳው ይችላል።"</string>
+ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ድምጹ ከሚመከረው መጠን በላይ ከፍ ይበል?\n\nበከፍተኛ ድምፅ ለረጅም ጊዜ ማዳመጥ ጆሮዎን ሊጎዳው ይችላል።"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"የተደራሽነት አቋራጭ ጥቅም ላይ ይዋል?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"አቋራጩ ሲበራ ሁለቱንም የድምጽ አዝራሮች ለ3 ሰከንዶች ተጭኖ መቆየት የተደራሽነት ባህሪን ያስጀምረዋል።"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"የተደራሽነት ባህሪዎች አቋራጭ ይብራ?"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 1280485cf04b..c2f24a541149 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -514,7 +514,7 @@
<string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"للسماح للتطبيق بالحصول على قائمة بالحسابات التي يعرفها الهاتف. وقد يتضمن ذلك أي حسابات تم إنشاؤها بواسطة التطبيقات التي ثبتها."</string>
<string name="permlab_accessNetworkState" msgid="2349126720783633918">"عرض اتصالات الشبكة"</string>
<string name="permdesc_accessNetworkState" msgid="4394564702881662849">"للسماح للتطبيق بعرض معلومات حول اتصالات الشبكة كعرض معلومات عن الشبكات المتوفرة والشبكات المتصلة."</string>
- <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"حق الوصول الكامل إلى الشبكة"</string>
+ <string name="permlab_createNetworkSockets" msgid="3224420491603590541">"الإذن بالوصول الكامل إلى الشبكة"</string>
<string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"للسماح للتطبيق بإنشاء مقابس شبكات واستخدام بروتوكولات شبكات مخصصة. ويوفر المتصفح وتطبيقات أخرى طرقًا لإرسال البيانات إلى الإنترنت، ولذلك لا يعد هذا الإذن مطلوبًا لإرسال البيانات إلى الإنترنت."</string>
<string name="permlab_changeNetworkState" msgid="8945711637530425586">"تغيير اتصال الشبكة"</string>
<string name="permdesc_changeNetworkState" msgid="649341947816898736">"للسماح للتطبيق بتغيير حالة اتصال الشبكة."</string>
@@ -1272,7 +1272,7 @@
<string name="dump_heap_ready_notification" msgid="2302452262927390268">"نَسْخ الذاكرة <xliff:g id="PROC">%1$s</xliff:g> جاهز"</string>
<string name="dump_heap_notification_detail" msgid="8431586843001054050">"تم جمع مقدار كبير من بيانات الذاكرة. انقر للمشاركة."</string>
<string name="dump_heap_title" msgid="4367128917229233901">"هل تريد مشاركة نَسْخ الذاكرة؟"</string>
- <string name="dump_heap_text" msgid="1692649033835719336">"تجاوزت عملية <xliff:g id="PROC">%1$s</xliff:g> حد الذاكرة المخصص لها وقدره <xliff:g id="SIZE">%2$s</xliff:g>، ويتوفر نَسْخ للذاكرة لمشاركته مع مطور برامج العملية ولكن توخ الحذر حيث قد يحتوي نَسْخ الذاكرة هذا على معلومات شخصية يملك التطبيق حق الوصول إليها."</string>
+ <string name="dump_heap_text" msgid="1692649033835719336">"تجاوزت عملية <xliff:g id="PROC">%1$s</xliff:g> حد الذاكرة المخصص لها وقدره <xliff:g id="SIZE">%2$s</xliff:g>، ويتوفر نَسْخ للذاكرة لمشاركته مع مطور برامج العملية ولكن توخ الحذر حيث قد يحتوي نَسْخ الذاكرة هذا على معلومات شخصية يملك التطبيق الإذن بالوصول إليها."</string>
<string name="dump_heap_system_text" msgid="6805155514925350849">"تجاوزت عملية <xliff:g id="PROC">%1$s</xliff:g> القيد المفروض على الذاكرة الذي يبلغ <xliff:g id="SIZE">%2$s</xliff:g>. ويتوفّر نَسْخ ذاكرة يمكنك مشاركته. تحذير: قد يحتوي نَسْخ الذاكرة هذا على معلومات شخصية حسّاسة يمكن للعملية الوصول إليها، وقد يتضمن معلومات سبق لك كتابتها."</string>
<string name="dump_heap_ready_text" msgid="5849618132123045516">"يتوفّر نَسْخ ذاكرة من عملية <xliff:g id="PROC">%1$s</xliff:g> حتى تتمكّن من مشاركته. تحذير: قد يحتوي نَسْخ الذاكرة هذا على معلومات شخصية حسّاسة يمكن للعملية الوصول إليها، وقد يتضمن معلومات سبق لك كتابتها."</string>
<string name="sendText" msgid="493003724401350724">"اختيار إجراء للنص"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index c9550c80f8c6..34cac8bfde03 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -171,7 +171,7 @@
<string name="httpErrorBadUrl" msgid="754447723314832538">"অমান্য URLৰ বাবে পৃষ্ঠাটো খুলিব পৰা নগ\'ল।"</string>
<string name="httpErrorFile" msgid="3400658466057744084">"ফাইলত খুলিব পৰা নগ\'ল।"</string>
<string name="httpErrorFileNotFound" msgid="5191433324871147386">"অনুৰোধ কৰা ফাইলটো বিচাৰি পোৱা নগ\'ল।"</string>
- <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"বহুত বেছি অনুৰোধৰ প্ৰক্ৰিয়া চলি আছে৷ অনুগ্ৰহ কৰি পিছত আকৌ চেষ্টা কৰক৷"</string>
+ <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"বহুত বেছি অনুৰোধৰ প্ৰক্ৰিয়া চলি আছে৷ অনুগ্ৰহ কৰি পাছত আকৌ চেষ্টা কৰক৷"</string>
<string name="notification_title" msgid="5783748077084481121">"<xliff:g id="ACCOUNT">%1$s</xliff:g>ত ছাইন ইন কৰাত আসোঁৱাহ"</string>
<string name="contentServiceSync" msgid="2341041749565687871">"ছিংক ত্ৰুটি"</string>
<string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"ছিংক কৰিব নোৱাৰি"</string>
@@ -203,7 +203,7 @@
<string name="device_policy_manager_service" msgid="5085762851388850332">"ডিভাইচৰ নীতিৰ পৰিচালক সেৱা"</string>
<string name="music_recognition_manager_service" msgid="7481956037950276359">"সংগীত চিনাক্তকৰণ পৰিচালক সেৱা"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"আপোনাৰ ডিভাইচৰ ডেটা মচা হ\'ব"</string>
- <string name="factory_reset_message" msgid="2657049595153992213">"এই প্ৰশাসক এপটো ব্যৱহাৰ কৰিব নোৱাৰি। এতিয়া আপোনাৰ ডিভাইচটোৰ ডেটা মচা হ\'ব।\n\nআপোনাৰ কিবা প্ৰশ্ন থাকিলে আপোনাৰ প্ৰতিষ্ঠানৰ প্ৰশাসকৰ সৈতে যোগাযোগ কৰক।"</string>
+ <string name="factory_reset_message" msgid="2657049595153992213">"এই প্ৰশাসক এপ্‌টো ব্যৱহাৰ কৰিব নোৱাৰি। এতিয়া আপোনাৰ ডিভাইচটোৰ ডেটা মচা হ\'ব।\n\nআপোনাৰ কিবা প্ৰশ্ন থাকিলে আপোনাৰ প্ৰতিষ্ঠানৰ প্ৰশাসকৰ সৈতে যোগাযোগ কৰক।"</string>
<string name="printing_disabled_by" msgid="3517499806528864633">"প্ৰিণ্ট কৰা কাৰ্য <xliff:g id="OWNER_APP">%s</xliff:g>এ অক্ষম কৰি ৰাখিছে।"</string>
<string name="personal_apps_suspension_title" msgid="7561416677884286600">"কৰ্মস্থানৰ প্ৰ’ফাইলটো অন কৰক"</string>
<string name="personal_apps_suspension_text" msgid="6115455688932935597">"আপুনি নিজৰ কৰ্মস্থানৰ প্ৰ’ফাইলটো অন নকৰালৈকে আপোনাৰ ব্যক্তিগত এপ্‌সমূহ অৱৰোধ কৰা থাকে"</string>
@@ -342,23 +342,23 @@
<string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"স্ক্ৰীনশ্বট লওক"</string>
<string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"ডিছপ্লে’খনৰ এটা স্ক্ৰীনশ্বট ল\'ব পাৰে।"</string>
<string name="permlab_statusBar" msgid="8798267849526214017">"স্থিতি দণ্ড অক্ষম কৰক বা সলনি কৰক"</string>
- <string name="permdesc_statusBar" msgid="5809162768651019642">"স্থিতি দণ্ড অক্ষম কৰিবলৈ বা ছিষ্টেম আইকন আঁতৰাবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_statusBar" msgid="5809162768651019642">"স্থিতি দণ্ড অক্ষম কৰিবলৈ বা ছিষ্টেম আইকন আঁতৰাবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_statusBarService" msgid="2523421018081437981">"স্থিতি দণ্ড হ\'ব পাৰে"</string>
- <string name="permdesc_statusBarService" msgid="6652917399085712557">"নিজকে স্থিতি দণ্ডৰূপে দেখুওৱাবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_statusBarService" msgid="6652917399085712557">"নিজকে স্থিতি দণ্ডৰূপে দেখুওৱাবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_expandStatusBar" msgid="1184232794782141698">"স্থিতি দণ্ড সম্প্ৰসাৰিত বা সংকোচিত কৰক"</string>
- <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"স্থিতি দণ্ড বিস্তাৰিত বা সংকুচিত কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_expandStatusBar" msgid="7180756900448498536">"স্থিতি দণ্ড বিস্তাৰিত বা সংকুচিত কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<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="permdesc_uninstall_shortcut" msgid="1924735350988629188">"ব্যৱহাৰকাৰীৰ হস্তক্ষেপৰ অবিহনে গৃহ স্ক্ৰীনৰ শ্বৰ্টকাটসমূহ আঁতৰাবলৈ এপ্লিকেশ্বনক অনুমতি দিয়ে।"</string>
<string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"বহিৰ্গামী কলসমূহ অন্য ক\'ৰবালৈ পঠিয়াওক"</string>
<string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"এটা বৰ্হিগামী কল কৰি থকাৰ সময়ত ডায়েল কৰা নম্বৰ চাবলৈ আৰু লগতে এটা পৃথক নম্বৰলৈ কল সংযোগ কৰিবলৈ বা সকলোকে একেলগে বন্ধ কৰিবলৈ এপক অনুমতি দিয়ে।"</string>
<string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"ফ\'ন কলৰ উত্তৰ দিব পাৰে"</string>
- <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"এপটোক অন্তৰ্গামী ফ\'ন কলৰ উত্তৰ দিবলৈ অনুমতি দিয়ে।"</string>
+ <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"এপ্‌টোক অন্তৰ্গামী ফ\'ন কলৰ উত্তৰ দিবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_receiveSms" msgid="505961632050451881">"পাঠ বার্তা (এছএমএছ) বোৰ লাভ কৰক"</string>
- <string name="permdesc_receiveSms" msgid="1797345626687832285">"এপটোক এছএমএছ বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ ইয়াৰ অৰ্থ এইটোৱেই যে এপটোৱে আপোনাক বাৰ্তাবোৰ নেদেখুৱাকৈয়ে আপোনাৰ ডিভাইচলৈ পঠিওৱা বাৰ্তাবোৰ নিৰীক্ষণ কৰিব বা মচিব পাৰে৷"</string>
+ <string name="permdesc_receiveSms" msgid="1797345626687832285">"এপ্‌টোক এছএমএছ বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ ইয়াৰ অৰ্থ এইটোৱেই যে এপটোৱে আপোনাক বাৰ্তাবোৰ নেদেখুৱাকৈয়ে আপোনাৰ ডিভাইচলৈ পঠিওৱা বাৰ্তাবোৰ নিৰীক্ষণ কৰিব বা মচিব পাৰে৷"</string>
<string name="permlab_receiveMms" msgid="4000650116674380275">"পাঠ বার্তা (এমএমএছ) বোৰ লাভ কৰক"</string>
<string name="permdesc_receiveMms" msgid="958102423732219710">"এমএমএছ বার্তাবোৰ লাভ আৰু ইয়াৰ প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ এপক অনুমতি দিয়ে। ইয়াৰ অৰ্থ হৈছে এই এপে আপোনাৰ ডিভাইচলৈ প্ৰেৰণ কৰা বার্তাসমূহ আপোনাক নেদেখুৱাকৈয়ে পৰ্যবেক্ষণ আৰু মচিব পাৰে।"</string>
<string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"চেল সম্প্ৰচাৰ বাৰ্তাসমূহ ফৰৱাৰ্ড কৰক"</string>
@@ -368,26 +368,26 @@
<string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"চেল সম্প্ৰচাৰৰ বার্তাবোৰ পঢ়ক"</string>
<string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"আপোনাৰ ডিভাইচে লাভ কৰা চেল সম্প্ৰচাৰৰ বার্তাবোৰ পঢ়িবলৈ এপক অনুমতি দিয়ে। আপোনাক জৰুৰীকালীন পৰিস্থিতিবোৰত সর্তক কৰিবলৈ চেল সম্প্ৰচাৰৰ বার্তাবোৰ প্ৰেৰণ কৰা হয়। জৰুৰীকালীন চেল সম্প্ৰচাৰ লাভ কৰাৰ সময়ত আপোনাৰ ডিভাইচৰ কাৰ্যদক্ষতা বা কাৰ্যপ্ৰণালীত ক্ষতিকাৰক এপবোৰে হস্তক্ষেপ কৰিব পাৰে।"</string>
<string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"আপুনি সদস্যভুক্ত হোৱা ফীডসমূহ পঢ়ক"</string>
- <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"বৰ্তমান ছিংক কৰা ফীডৰ সবিশেষ লাভ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"বৰ্তমান ছিংক কৰা ফীডৰ সবিশেষ লাভ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_sendSms" msgid="7757368721742014252">"এছএমএছ ৰ বার্তাবোৰ প্ৰেৰণ কৰিব আৰু চাব পাৰে"</string>
- <string name="permdesc_sendSms" msgid="6757089798435130769">"এপটোক এছএমএছ বাৰ্তা পঠিয়াবলৈ অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা হ\'ব পাৰে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে বাৰ্তা পঠিয়াই আপোনাৰ পৰা মাচুল কাটিব পাৰে৷"</string>
+ <string name="permdesc_sendSms" msgid="6757089798435130769">"এপ্‌টোক এছএমএছ বাৰ্তা পঠিয়াবলৈ অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা হ\'ব পাৰে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে বাৰ্তা পঠিয়াই আপোনাৰ পৰা মাচুল কাটিব পাৰে৷"</string>
<string name="permlab_readSms" msgid="5164176626258800297">"আপোনাৰ পাঠ বার্তাবোৰ পঢ়ক (এছএমএছ বা এমএমএছ)"</string>
<string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"এই এপ্‌টোৱে আপোনাৰ টেবলেটটোত সংৰক্ষিত আটাইবোৰ এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string>
<string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"এই এপ্‌টোৱে আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা আটাইবোৰ এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string>
<string name="permdesc_readSms" product="default" msgid="774753371111699782">"এই এপ্‌টোৱে আপোনাৰ ফ\'নত সংৰক্ষিত আটাইবোৰ এছএমএছ (পাঠ) বাৰ্তা পঢ়িব পাৰে।"</string>
<string name="permlab_receiveWapPush" msgid="4223747702856929056">"পাঠ বার্তা (WAP) বোৰ লাভ কৰক"</string>
- <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"এপটোক WAP বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ এই অনুমতিত আপোনালৈ পঠিওৱা বাৰ্তাবোৰ আপোনাক নেদেখুৱাকৈয়ে নিৰীক্ষণ বা মচাৰ সক্ষমতা অন্তৰ্ভুক্ত থাকে৷"</string>
+ <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"এপ্‌টোক WAP বাৰ্তাবোৰ পাবলৈ আৰু প্ৰক্ৰিয়া সম্পন্ন কৰিবলৈ অনুমতি দিয়ে৷ এই অনুমতিত আপোনালৈ পঠিওৱা বাৰ্তাবোৰ আপোনাক নেদেখুৱাকৈয়ে নিৰীক্ষণ বা মচাৰ সক্ষমতা অন্তৰ্ভুক্ত থাকে৷"</string>
<string name="permlab_getTasks" msgid="7460048811831750262">"চলি থকা এপসমূহ বিচাৰি উলিয়াওক"</string>
- <string name="permdesc_getTasks" msgid="7388138607018233726">"এপটোক বৰ্তমানে আৰু শেহতীয়াভাৱে চলি থকা কাৰ্যসমূহৰ বিষয়ে তথ্য পুনৰুদ্ধাৰ কৰিবলৈ অনুমতি দিয়ে৷ এইটোৱে এপটোক ডিভাইচটোত কোনবোৰ এপ্লিকেশ্বন ব্যৱহাৰ হৈ আছে তাৰ বিষয়ে তথ্য বিচাৰি উলিয়াবলৈ অনুমতি দিব পাৰে৷"</string>
+ <string name="permdesc_getTasks" msgid="7388138607018233726">"এপ্‌টোক বৰ্তমানে আৰু শেহতীয়াভাৱে চলি থকা কাৰ্যসমূহৰ বিষয়ে তথ্য পুনৰুদ্ধাৰ কৰিবলৈ অনুমতি দিয়ে৷ এইটোৱে এপ্‌টোক ডিভাইচটোত কোনবোৰ এপ্লিকেশ্বন ব্যৱহাৰ হৈ আছে তাৰ বিষয়ে তথ্য বিচাৰি উলিয়াবলৈ অনুমতি দিব পাৰে৷"</string>
<string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"প্ৰ\'ফাইল আৰু ডিভাইচৰ গৰাকীসকলক পৰিচালনা কৰিব পাৰে"</string>
- <string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"প্ৰ\'ফাইলৰ গৰাকী আৰু ডিভাইচৰ গৰাকী ছেট কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"প্ৰ\'ফাইলৰ গৰাকী আৰু ডিভাইচৰ গৰাকী ছেট কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_reorderTasks" msgid="7598562301992923804">"চলি থকা এপসমূহক পুনৰাই ক্ৰমবদ্ধ কৰক"</string>
<string name="permdesc_reorderTasks" msgid="8796089937352344183">"গতিবিধিক অগ্ৰভাগ আৰু নেপথ্যলৈ নিবলৈ এপক অনুমতি দিয়ে। এপে এই কার্য আপোনাৰ ইনপুট অবিহনেই কৰিব পাৰে।"</string>
<string name="permlab_enableCarMode" msgid="893019409519325311">"গাড়ীৰ ম\'ড সক্ষম কৰক"</string>
- <string name="permdesc_enableCarMode" msgid="56419168820473508">"গাড়ী ম\'ড সক্ষম কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+ <string name="permdesc_enableCarMode" msgid="56419168820473508">"গাড়ী ম\'ড সক্ষম কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷"</string>
<string name="permlab_killBackgroundProcesses" msgid="6559320515561928348">"অন্য এপবোৰ বন্ধ কৰক"</string>
- <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"এপটোক অন্য এপসমূহৰ নেপথ্যৰ প্ৰক্ৰিয়াসমূহ শেষ কৰিবলৈ অনুমতি দিয়ে৷ এই কার্যৰ বাবে অন্য এপসমূহ চলাটো বন্ধ হ\'ব পাৰে৷"</string>
- <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"এই এপটো অইন এপৰ ওপৰত প্ৰদৰ্শিত হ\'ব পাৰে"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"এপ্‌টোক অন্য এপসমূহৰ নেপথ্যৰ প্ৰক্ৰিয়াসমূহ শেষ কৰিবলৈ অনুমতি দিয়ে৷ এই কার্যৰ বাবে অন্য এপসমূহ চলাটো বন্ধ হ\'ব পাৰে৷"</string>
+ <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"এই এপ্‌টো অইন এপৰ ওপৰত প্ৰদৰ্শিত হ\'ব পাৰে"</string>
<string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"এই এপ্‌টো অন্য এপৰ ওপৰত বা স্ক্ৰীনৰ অন্য অংশত প্ৰদৰ্শিত হ\'ব পাৰে। এই কাৰ্যই এপৰ স্বাভাৱিক ব্যৱহাৰত ব্যাঘাত জন্মাব পাৰে আৰু অন্য এপ্‌সমূহক স্ক্ৰীনত কেনেকৈ দেখা পোৱা যায় সেইটো সলনি কৰিব পাৰে।"</string>
<string name="permlab_runInBackground" msgid="541863968571682785">"নেপথ্যত চলিব পাৰে"</string>
<string name="permdesc_runInBackground" msgid="4344539472115495141">"এই এপ্‌টো নেপথ্যত চলিব পাৰে। ইয়াৰ ফলত বেটাৰী সোনকালে শেষ হ’ব পাৰে।"</string>
@@ -398,15 +398,15 @@
<string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"এপ্‌টোক মেম’ৰীত নিজৰ বাবে প্ৰয়োজনীয় ঠাই পৃথক কৰিবলৈ অনুমতি দিয়ে। এই কার্যই আপোনাৰ Android TV ডিভাইচটোক লেহেমীয়া কৰি অন্য এপ্‌সমূহৰ বাবে উপলব্ধ মেম’ৰীক সীমাবদ্ধ কৰিব পাৰে।"</string>
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"মেম\'ৰিত নিজৰ বাবে প্ৰয়োজনীয় ঠাই পৃথক কৰিবলৈ এপক অনুমতি দিয়ে। এই কার্যই ফ\'নৰ কার্যক লেহেমীয়া কৰি অন্য এপবোৰৰ বাবে উপলব্ধ মেম\'ৰিক সীমাবদ্ধ কৰে।"</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"অগ্ৰভূমিৰ সেৱা চলাব পাৰে"</string>
- <string name="permdesc_foregroundService" msgid="8720071450020922795">"এপটোক অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে।"</string>
+ <string name="permdesc_foregroundService" msgid="8720071450020922795">"এপ্‌টোক অগ্ৰভূমি সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"এপৰ ষ্ট’ৰেজৰ খালী ঠাই হিচাপ কৰক"</string>
- <string name="permdesc_getPackageSize" msgid="742743530909966782">"এপটোক ইয়াৰ ক\'ড, ডেটা আৰু কেশ্বৰ আকাৰ বিচাৰি উলিয়াবলৈ অনুমতি দিয়ে"</string>
+ <string name="permdesc_getPackageSize" msgid="742743530909966782">"এপ্‌টোক ইয়াৰ ক\'ড, ডেটা আৰু কেশ্বৰ আকাৰ বিচাৰি উলিয়াবলৈ অনুমতি দিয়ে"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"ছিষ্টেম ছেটিংহ সংশোধন কৰক"</string>
<string name="permdesc_writeSettings" msgid="8293047411196067188">"এপ্‌টোক ছিষ্টেমৰ ছেটিঙৰ ডেটা সংশোধন কৰিবলৈ অনুমতি দিয়ে৷ ক্ষতিকাৰক এপ্‌সমূহে আপোনাৰ ছিষ্টেম কনফিগাৰেশ্বনক ক্ষতিগ্ৰস্ত কৰিব পাৰে৷"</string>
<string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"আৰম্ভ হোৱাৰ সময়ত চলাওক"</string>
- <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"ছিষ্টেমে বুট কৰা কাৰ্য সমাপ্ত কৰাৰ লগে লগে এপটোক নিজে নিজে আৰম্ভ হ\'বলৈ অনুমতি দিয়ে। ইয়াৰ ফলত ফ\'নটো ষ্টাৰ্ট হওতে বেছি সময়ৰ প্ৰয়োজন হ\'ব পাৰে, আৰু এপটো সদায় চলি থকাৰ কাৰণে ফ\'নটো সামগ্ৰিকভাৱে লেহেমীয়া হ\'ব পাৰে।"</string>
+ <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"ছিষ্টেমে বুট কৰা কাৰ্য সমাপ্ত কৰাৰ লগে লগে এপ্‌টোক নিজে নিজে আৰম্ভ হ\'বলৈ অনুমতি দিয়ে। ইয়াৰ ফলত ফ\'নটো ষ্টাৰ্ট হওতে বেছি সময়ৰ প্ৰয়োজন হ\'ব পাৰে, আৰু এপ্‌টো সদায় চলি থকাৰ কাৰণে ফ\'নটো সামগ্ৰিকভাৱে লেহেমীয়া হ\'ব পাৰে।"</string>
<string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"ছিষ্টেমে বুটিং সমাপ্ত কৰাৰ লগে লগে এই এপ্‌টোক নিজে নিজে আৰম্ভ হ’বলৈ অনুমতি দিয়ে। এই কাৰ্যৰ বাবে আপোনাৰ Android TV ডিভাইচটো আৰম্ভ হ’বলৈ দীঘলীয়া সময়ৰ প্ৰয়োজন হ’ব পাৰে আৰু সকলো সময়তে চলি থাকি এপ্‌টোক সামগ্ৰিকভাৱে ডিভাইচটো লেহেমীয়া কৰিবলৈ দিয়ে।"</string>
- <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"ছিষ্টেমে বুট কৰা কাৰ্য সমাপ্ত কৰাৰ লগে লগে এপটোক নিজে নিজে আৰম্ভ হ\'বলৈ অনুমতি দিয়ে। ইয়াৰ ফলত ফ\'নটো ষ্টাৰ্ট হওতে বেছি সময়ৰ প্ৰয়োজন হ\'ব পাৰে, আৰু এপটো সদায় চলি থকাৰ কাৰণে ফ\'নটো সামগ্ৰিকভাৱে লেহেমীয়া হ\'ব পাৰে।"</string>
+ <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"ছিষ্টেমে বুট কৰা কাৰ্য সমাপ্ত কৰাৰ লগে লগে এপ্‌টোক নিজে নিজে আৰম্ভ হ\'বলৈ অনুমতি দিয়ে। ইয়াৰ ফলত ফ\'নটো ষ্টাৰ্ট হওতে বেছি সময়ৰ প্ৰয়োজন হ\'ব পাৰে, আৰু এপ্‌টো সদায় চলি থকাৰ কাৰণে ফ\'নটো সামগ্ৰিকভাৱে লেহেমীয়া হ\'ব পাৰে।"</string>
<string name="permlab_broadcastSticky" msgid="4552241916400572230">"ষ্টিকী ব্ৰ\'ডকাষ্ট পঠিয়াওক"</string>
<string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"সম্প্ৰচাৰৰ শেষত বাকী ৰোৱা ষ্টিকী ব্ৰ\'ডকাষ্টবোৰ প্ৰেৰণ কৰিবলৈ এপক অনুমতি দিয়ে। ইয়াক অত্য়ধিক ব্যৱহাৰ কৰাৰ ফলত মেম\'ৰি অধিক খৰচ হোৱাৰ বাবে টেবলেট লেহেমীয়া বা অস্থিৰ হৈ পৰে।"</string>
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"এপ্‌টোক ব্ৰ’ডকাষ্ট শেষ হোৱাৰ পাছত বাকী থকা ষ্টিকী ব্ৰ’ডকাষ্টবোৰ পঠিয়াবলৈ অনুমতি দিয়ে। ইয়াক অত্যধিক ব্যৱহাৰ কৰিলে আপোনাৰ Android TV ডিভাইচটোক অতি বেছি পৰিমাণৰ মেম’ৰী খৰচ কৰাই লেহেমীয়া অথবা অস্থিৰ কৰিব পাৰে।"</string>
@@ -464,62 +464,62 @@
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"কোনো এপ্লিকেশ্বন অথবা সেৱাক কেমেৰা ডিভাইচসমূহ খোলা অথবা বন্ধ কৰাৰ বিষয়ে কলবেকসমূহ গ্ৰহণ কৰিবলৈ অনুমতি দিয়ক।"</string>
<string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"যিকোনো কেমেৰা ডিভাইচ খুলি থকা অথবা বন্ধ কৰি থকাৰ সময়ত (কোনো এপ্লিকেশ্বনৰ দ্বাৰা) এই এপ্‌টোৱে কলবেক গ্ৰহণ কৰিব পাৰে।"</string>
<string name="permlab_vibrate" msgid="8596800035791962017">"কম্পন নিয়ন্ত্ৰণ কৰক"</string>
- <string name="permdesc_vibrate" msgid="8733343234582083721">"ভাইব্ৰেটৰ নিয়ন্ত্ৰণ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_vibrate" msgid="8733343234582083721">"ভাইব্ৰেটৰ নিয়ন্ত্ৰণ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permdesc_vibrator_state" msgid="7050024956594170724">"এপ্‌টোক কম্পন স্থিতিটো এক্সেছ কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_callPhone" msgid="1798582257194643320">"পোনপটীয়াকৈ ফ\'ন নম্বৰলৈ কল কৰক"</string>
- <string name="permdesc_callPhone" msgid="5439809516131609109">"আপোনাৰ কোনো ব্যাঘাত নোহোৱাকৈ ফ\'ন নম্বৰবোৰত কল কৰিবলৈ এপক অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা বা কলবোৰ কৰা হ\'ব পাৰে৷ মনত ৰাখিব যে ই এপটোক জৰুৰীকালীন নম্বৰবোৰত কল কৰিবলৈ অনুমতি নিদিয়ে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে কল কৰি আপোনাক টকা খৰছ কৰাব পাৰে৷"</string>
+ <string name="permdesc_callPhone" msgid="5439809516131609109">"আপোনাৰ কোনো ব্যাঘাত নোহোৱাকৈ ফ\'ন নম্বৰবোৰত কল কৰিবলৈ এপক অনুমতি দিয়ে৷ ইয়াৰ ফলত অপ্ৰত্যাশিত মাচুল ভৰিবলগা বা কলবোৰ কৰা হ\'ব পাৰে৷ মনত ৰাখিব যে ই এপ্‌টোক জৰুৰীকালীন নম্বৰবোৰত কল কৰিবলৈ অনুমতি নিদিয়ে৷ ক্ষতিকাৰক এপসমূহে আপোনাৰ অনুমতি নোলোৱাকৈয়ে কল কৰি আপোনাক টকা খৰছ কৰাব পাৰে৷"</string>
<string name="permlab_accessImsCallService" msgid="442192920714863782">"আইএমএছ কল সেৱা ব্যৱহাৰ কৰিব পাৰে"</string>
<string name="permdesc_accessImsCallService" msgid="6328551241649687162">"আপোনাৰ হস্তক্ষেপৰ অবিহনে আইএমএছ সেৱা ব্যৱহাৰ কৰি কল কৰিবলৈ এপক অনুমতি দিয়ে।"</string>
<string name="permlab_readPhoneState" msgid="8138526903259297969">"ফ\'নৰ স্থিতি আৰু পৰিচয় পঢ়ক"</string>
- <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ডিভাইচত থকা ফ\'নৰ সুবিধাসমূহ ব্যৱহাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে৷ এই অনুমতিয়ে কোনো কল সক্ৰিয় হৈ থাককেই বা নাথাকক আৰু দূৰবৰ্তী নম্বৰটো কলৰ দ্বাৰা সংযোজিত হওকেই বা নহওক এপটোক ফ\'ন নম্বৰ আৰু ডিভাইচৰ পৰিচয় নিৰ্ধাৰণ কৰিবলৈ অনুমতি দিয়ে৷"</string>
+ <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ডিভাইচত থকা ফ\'নৰ সুবিধাসমূহ ব্যৱহাৰ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷ এই অনুমতিয়ে কোনো কল সক্ৰিয় হৈ থাককেই বা নাথাকক আৰু দূৰবৰ্তী নম্বৰটো কলৰ দ্বাৰা সংযোজিত হওকেই বা নহওক এপ্‌টোক ফ\'ন নম্বৰ আৰু ডিভাইচৰ পৰিচয় নিৰ্ধাৰণ কৰিবলৈ অনুমতি দিয়ে৷"</string>
<string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"প্ৰাথমিক টেলিফ\'নী স্থিতি আৰু পৰিচয় পঢ়ক"</string>
<string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"এপ্‌টোক ডিভাইচটোৰ প্ৰাথমিক টেলিফ’নী সুবিধাসমূহ এক্সেছ কৰাৰ অনুমতি দিয়ে।"</string>
<string name="permlab_manageOwnCalls" msgid="9033349060307561370">"ছিষ্টেমৰ জৰিয়তে কল কৰিব পাৰে"</string>
- <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"কল কৰাৰ অভিজ্ঞতাক উন্নত কৰিবলৈ এপটোক ছিষ্টেমৰ জৰিয়তে কলসমূহ কৰিবলৈ দিয়ে।"</string>
+ <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"কল কৰাৰ অভিজ্ঞতাক উন্নত কৰিবলৈ এপ্‌টোক ছিষ্টেমৰ জৰিয়তে কলসমূহ কৰিবলৈ দিয়ে।"</string>
<string name="permlab_callCompanionApp" msgid="3654373653014126884">"ছিষ্টেমৰ জৰিয়তে কলবোৰ চোৱা আৰু নিয়ন্ত্ৰণ কৰা।"</string>
- <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"এপটোক ডিভাইচত চলি থকা কল চাবলৈ আৰু নিয়ন্ত্ৰণ কৰিবলৈ অনুমতি দিয়ে। কলৰ সংখ্যা আৰু কলবোৰৰ স্থিতি ইয়াত অন্তৰ্ভুক্ত হয়।"</string>
+ <string name="permdesc_callCompanionApp" msgid="8474168926184156261">"এপ্‌টোক ডিভাইচত চলি থকা কল চাবলৈ আৰু নিয়ন্ত্ৰণ কৰিবলৈ অনুমতি দিয়ে। কলৰ সংখ্যা আৰু কলবোৰৰ স্থিতি ইয়াত অন্তৰ্ভুক্ত হয়।"</string>
<string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"অডিঅ’ ৰেকৰ্ড কৰাৰ প্ৰতিবন্ধকতাসমূহৰ পৰা ৰেহাই দিয়ক"</string>
<string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"অডিঅ’ ৰেকৰ্ড কৰাৰ প্ৰতিবন্ধকতাসমূহৰ পৰা এপ্‌টোক ৰেহাই দিয়ক।"</string>
<string name="permlab_acceptHandover" msgid="2925523073573116523">"অইন এটা এপত আৰম্ভ হোৱা কল এটা অব্যাহত ৰাখিব পাৰে"</string>
- <string name="permdesc_acceptHandovers" msgid="7129026180128626870">"এপটোক এনে কল কৰিবলৈ দিয়ে যিটোৰ আৰম্ভণি অইন এটা এপত হৈছিল।"</string>
+ <string name="permdesc_acceptHandovers" msgid="7129026180128626870">"এপ্‌টোক এনে কল কৰিবলৈ দিয়ে যিটোৰ আৰম্ভণি অইন এটা এপত হৈছিল।"</string>
<string name="permlab_readPhoneNumbers" msgid="5668704794723365628">"ফ\'ন নম্বৰসমূহ পঢ়ে"</string>
- <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"এপটোক ডিভাইচটোৰ ফ\'ন নম্বৰসমূহ চাবলৈ অনুমতি দিয়ে।"</string>
+ <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"এপ্‌টোক ডিভাইচটোৰ ফ\'ন নম্বৰসমূহ চাবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_wakeLock" product="automotive" msgid="1904736682319375676">"গাড়ীৰ স্ক্রীনখন অন কৰি ৰখা"</string>
<string name="permlab_wakeLock" product="tablet" msgid="1527660973931694000">"টে\'বলেট সুপ্ত অৱস্থালৈ যোৱাত বাধা দিয়ক"</string>
<string name="permlab_wakeLock" product="tv" msgid="2856941418123343518">"আপোনাৰ Android TV ডিভাইচটো সুপ্ত অৱস্থালৈ যোৱাত বাধা দিয়ক"</string>
<string name="permlab_wakeLock" product="default" msgid="569409726861695115">"ফ\'ন সুপ্ত অৱস্থালৈ যোৱাত বাধা দিয়ক"</string>
<string name="permdesc_wakeLock" product="automotive" msgid="5995045369683254571">"এপ্‌টোক গাড়ীৰ স্ক্রীনখন অন কৰি ৰাখিবলৈ অনুমতি দিয়ে।"</string>
- <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"টে\'বলেট সুপ্ত অৱস্থালৈ যোৱাৰ পৰা প্ৰতিৰোধ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"টে\'বলেট সুপ্ত অৱস্থালৈ যোৱাৰ পৰা প্ৰতিৰোধ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচটো সুপ্ত অৱস্থালৈ যোৱাত বাধা দিবলৈ অনুমতি দিয়ে।"</string>
- <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"ফ\'ন সুপ্ত অৱস্থালৈ যোৱাৰ পৰা প্ৰতিৰোধ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"ফ\'ন সুপ্ত অৱস্থালৈ যোৱাৰ পৰা প্ৰতিৰোধ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_transmitIr" msgid="8077196086358004010">"ইনফ্ৰাৰেড ট্ৰান্সমিট কৰিব পাৰে"</string>
<string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"টে\'বলেটৰ ইনফ্ৰাৰেড ট্ৰান্সমিটাৰ ব্যৱহাৰ কৰিবলৈ এপক অনুমতি দিয়ে।"</string>
<string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচৰ ইনফ্ৰাৰেড ট্ৰান্সমিটাৰ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"ফ\'নৰ ইনফ্ৰাৰেড ট্ৰান্সমিটাৰ ব্যৱহাৰ কৰিবলৈ এপক অনুমতি দিয়ে।"</string>
<string name="permlab_setWallpaper" msgid="6959514622698794511">"ৱালপেপাৰ ছেট কৰক"</string>
- <string name="permdesc_setWallpaper" msgid="2973996714129021397">"ছিষ্টেমৰ ৱালপেপাৰ ছেট কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_setWallpaper" msgid="2973996714129021397">"ছিষ্টেমৰ ৱালপেপাৰ ছেট কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_setWallpaperHints" msgid="1153485176642032714">"আপোনাৰ ৱালপেপাৰৰ আকাৰ মিলাওক"</string>
- <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"ছিষ্টেমৰ ৱালপেপাৰৰ আকাৰ হিণ্ট ছেট কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"ছিষ্টেমৰ ৱালপেপাৰৰ আকাৰ হিণ্ট ছেট কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_setTimeZone" msgid="7922618798611542432">"সময় মণ্ডল ছেট কৰক"</string>
- <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"টে\'বলেটৰ সময় মণ্ডল সলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"টে\'বলেটৰ সময় মণ্ডল সলনি কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচটোৰ সময় মণ্ডল সলনি কৰিবলৈ অনুমতি দিয়ে।"</string>
- <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"ফ\'নৰ সময় মণ্ডল সলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"ফ\'নৰ সময় মণ্ডল সলনি কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_getAccounts" msgid="5304317160463582791">"ডিভাইচত একাউণ্টবোৰ বিচাৰক"</string>
- <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"এপটোক টেবলেটটোৰ জ্ঞাত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে৷ এইটোৱে আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্টক অন্তৰ্ভুক্ত কৰিব পাৰে৷"</string>
+ <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"এপ্‌টোক টেবলেটটোৰ জ্ঞাত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে৷ এইটোৱে আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্টক অন্তৰ্ভুক্ত কৰিব পাৰে৷"</string>
<string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"আপোনাৰ Android TV ডিভাইচটোৰ পৰিচিত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে। আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্ট অন্তৰ্ভুক্ত হ’ব পাৰে।"</string>
- <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"এপটোক ফ\'নটোৰ জ্ঞাত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে৷ এইটোৱে আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্টক অন্তৰ্ভুক্ত কৰিব পাৰে৷"</string>
+ <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"এপ্‌টোক ফ\'নটোৰ জ্ঞাত একাউণ্টসমূহৰ সূচীখন পাবলৈ অনুমতি দিয়ে৷ এইটোৱে আপুনি ইনষ্টল কৰি ৰখা এপ্লিকেশ্বনসমূহে সৃষ্টি কৰা যিকোনো একাউণ্টক অন্তৰ্ভুক্ত কৰিব পাৰে৷"</string>
<string name="permlab_accessNetworkState" msgid="2349126720783633918">"নেটৱৰ্কৰ সংযোগবোৰ চাওক"</string>
- <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"মজুত থকা আৰু সংযোগ হৈ থকা নেটৱৰ্ক সংযোগসমূহৰ বিষয়ে তথ্য চাবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+ <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"মজুত থকা আৰু সংযোগ হৈ থকা নেটৱৰ্ক সংযোগসমূহৰ বিষয়ে তথ্য চাবলৈ এপ্‌টোক অনুমতি দিয়ে৷"</string>
<string name="permlab_createNetworkSockets" msgid="3224420491603590541">"সম্পূর্ণ নেটৱর্কৰ সুবিধা লাভ কৰিব পাৰে"</string>
- <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"এপটোক নেটৱৰ্ক ছ\'কেটবোৰ সৃষ্টি কৰিবলৈ আৰু কাষ্টম নেটৱৰ্ক প্ৰ\'ট\'কল ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে৷ ব্ৰাউজাৰ আৰু অন্য এপ্লিকেশ্বনসমূহে ইণ্টাৰনেটলৈ ডেটা পঠিওৱা মাধ্য়ম প্ৰদান কৰে, গতিকে ইণ্টাৰনেটলৈ ডেটা পঠিয়াবলৈ এই অনুমতিৰ প্ৰয়োজন নাই৷"</string>
+ <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"এপ্‌টোক নেটৱৰ্ক ছ\'কেটবোৰ সৃষ্টি কৰিবলৈ আৰু কাষ্টম নেটৱৰ্ক প্ৰ\'ট\'কল ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে৷ ব্ৰাউজাৰ আৰু অন্য এপ্লিকেশ্বনসমূহে ইণ্টাৰনেটলৈ ডেটা পঠিওৱা মাধ্য়ম প্ৰদান কৰে, গতিকে ইণ্টাৰনেটলৈ ডেটা পঠিয়াবলৈ এই অনুমতিৰ প্ৰয়োজন নাই৷"</string>
<string name="permlab_changeNetworkState" msgid="8945711637530425586">"নেটৱৰ্কৰ সংযোগ সলনি কৰক"</string>
- <string name="permdesc_changeNetworkState" msgid="649341947816898736">"নেটৱৰ্ক সংযোগৰ অৱস্থাটো সলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_changeNetworkState" msgid="649341947816898736">"নেটৱৰ্ক সংযোগৰ অৱস্থাটো সলনি কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_changeTetherState" msgid="9079611809931863861">"টেডাৰিং সংযোগ সলনি কৰক"</string>
- <string name="permdesc_changeTetherState" msgid="3025129606422533085">"টেডাৰ হৈ থকা ইণ্টাৰনেট সংযোগৰ অৱস্থা সলনি কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+ <string name="permdesc_changeTetherState" msgid="3025129606422533085">"টেডাৰ হৈ থকা ইণ্টাৰনেট সংযোগৰ অৱস্থা সলনি কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷"</string>
<string name="permlab_accessWifiState" msgid="5552488500317911052">"ৱাই-ফাইৰ সংযোগবোৰ চাওক"</string>
<string name="permdesc_accessWifiState" msgid="6913641669259483363">"ৱাই-ফাই সক্ষম কৰা হ’ল নে নাই আৰু সংযোগ হৈ থকা ৱাই-ফাই ডিভাইচসমূহৰ নামবোৰৰ দৰে ৱাই-ফাইৰ ইণ্টাৰনেট সম্পর্কীয় তথ্য চাবলৈ এপক অনুমতি দিয়ে।"</string>
<string name="permlab_changeWifiState" msgid="7947824109713181554">"ৱাই-ফাই সংযোগ কৰক আৰু ইয়াৰ সংযোগ বিচ্ছিন্ন কৰক"</string>
- <string name="permdesc_changeWifiState" msgid="7170350070554505384">"এপটোক ৱাই-ফাই এক্সেছ পইণ্টলৈ সংযোগ কৰিবলৈ আৰু তাৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ আৰু ৱাই-ফাই নেটৱৰ্কসমূহৰ বাবে ডিভাইচ কনফিগাৰেশ্বনত সাল-সলনি কৰিবলৈ অনুমতি দিয়ে৷"</string>
+ <string name="permdesc_changeWifiState" msgid="7170350070554505384">"এপ্‌টোক ৱাই-ফাই এক্সেছ পইণ্টলৈ সংযোগ কৰিবলৈ আৰু তাৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ আৰু ৱাই-ফাই নেটৱৰ্কসমূহৰ বাবে ডিভাইচ কনফিগাৰেশ্বনত সাল-সলনি কৰিবলৈ অনুমতি দিয়ে৷"</string>
<string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"ৱাই-ফাই মাল্টিকাষ্ট প্ৰচাৰৰ অনুমতি দিয়ক"</string>
<string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"কেৱল আপোনাৰ টেবলেটটোৱেই নহয়, মাল্টিকাষ্ট ঠিকনা ব্যৱহাৰ কৰি এটা ৱাই-ফাই নেটৱর্কত থকা আটাইবোৰ ডিভাইচলৈ পঠিওৱা পেকেট লাভ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে। এই কার্যই নন মাল্টিকাষ্ট ম\'ডতকৈ অধিক বেটাৰী ব্যৱহাৰ কৰে।"</string>
<string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"কেৱল আপোনাৰ Android TV ডিভাইচটোৱেই নহয়, মাল্টিকাষ্ট ঠিকনাবোৰ ব্যৱহাৰ কৰি এটা ৱাই-ফাই নেটৱর্কত থকা আটাইবোৰ ডিভাইচলৈ পঠিওৱা পেকেট লাভ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে। এই কার্যই নন-মাল্টিকাষ্ট ম’ডতকৈ অধিক পাৱাৰ ব্যৱহাৰ কৰে।"</string>
@@ -529,15 +529,15 @@
<string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচটোত ব্লুটুথ কনফিগাৰ কৰিবলৈ আৰু ৰিম’ট ডিভাইচসমূহ বিচাৰি উলিয়াবলৈ আৰু পেয়াৰ কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"স্থানীয় ব্লুটুথ ফ’ন কনফিগাৰ কৰিবলৈ আৰু দূৰৱৰ্তী ডিভাইচসমূহৰ সৈতে পেয়াৰ কৰিবলৈ আৰু বিচাৰি উলিয়াবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_accessWimaxState" msgid="7029563339012437434">"WiMAXৰ লগত সংযোগ কৰক আৰু ইয়াৰ পৰা সংযোগ বিচ্ছিন্ন কৰক"</string>
- <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"WiMAX সক্ষম হৈ আছেনে নাই আৰু সংযোজিত যিকোনো WiMAX নেটৱৰ্কৰ বিষয়ে তথ্য নিৰ্ধাৰণ কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+ <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"WiMAX সক্ষম হৈ আছেনে নাই আৰু সংযোজিত যিকোনো WiMAX নেটৱৰ্কৰ বিষয়ে তথ্য নিৰ্ধাৰণ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷"</string>
<string name="permlab_changeWimaxState" msgid="6223305780806267462">"WiMAXৰ স্থিতি সলনি কৰক"</string>
- <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"এপটোক টেবলেটলৈ সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা টেবলেটৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে৷"</string>
+ <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"এপ্‌টোক টেবলেটলৈ সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা টেবলেটৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে৷"</string>
<string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচৰ সৈতে সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা আপোনাৰ Android TV ডিভাইচৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে।"</string>
- <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"এপটোক ফ\'নলৈ সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা ফ\'নৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে৷"</string>
+ <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"এপ্‌টোক ফ\'নলৈ সংযোগ কৰিবলৈ আৰু WiMAX নেটৱৰ্কসমূহৰ পৰা ফ\'নৰ সংযোগ বিচ্ছিন্ন কৰিবলৈ অনুমতি দিয়ে৷"</string>
<string name="permlab_bluetooth" msgid="586333280736937209">"ব্লুটুথ ডিভাইচবোৰৰ সৈতে পেয়াৰ কৰক"</string>
- <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"টেবলেটত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+ <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"টেবলেটত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচটোত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু পেয়াৰ কৰি থোৱা ডিভাইচসমূহৰ সৈতে সংযোগ কৰিবলৈ আৰু গ্ৰহণ কৰিবলৈ অনুমতি দিয়ে।"</string>
- <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ফ\'নটোত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+ <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ফ\'নটোত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷"</string>
<string name="permlab_bluetooth_scan" msgid="5402587142833124594">"নিকটৱৰ্তী ব্লুটুথ ডিভাইচ বিচাৰক আৰু তাৰ সৈতে পেয়াৰ কৰক"</string>
<string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"এপ্‌টোক নিকটৱৰ্তী ব্লুটুথ ডিভাইচ বিচাৰি উলিয়াবলৈ আৰু সেইসমূহৰ সৈতে পেয়াৰ কৰিবলৈ অনুমতি দিয়ে"</string>
<string name="permlab_bluetooth_connect" msgid="6657463246355003528">"পেয়াৰ কৰা ব্লুটুথ ডিভাইচৰ সৈতে সংযোগ কৰক"</string>
@@ -551,9 +551,9 @@
<string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"অগ্ৰাধিকাৰ দিয়া NFC পৰিশোধ সেৱাৰ তথ্য"</string>
<string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"এপ্‌টোক অগ্ৰাধিকাৰ দিয়া nfc পৰিশোধ সেৱাৰ পঞ্জীকৃত সহায়কসমূহ আৰু পৰিশোধ কৰিব লগা লক্ষ্যস্থান দৰে তথ্য পাবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_nfc" msgid="1904455246837674977">"নিয়েৰ ফিল্ড কমিউনিকেশ্বন নিয়ন্ত্ৰণ কৰক"</string>
- <string name="permdesc_nfc" msgid="8352737680695296741">"এপটোক নিয়েৰ ফিল্ড কমিউনিকেশ্বন (NFC) টেগ, কাৰ্ড আৰু ৰিডাৰসমূহৰ সৈতে যোগাযোগ কৰিবলৈ অনুমতি দিয়ে।"</string>
+ <string name="permdesc_nfc" msgid="8352737680695296741">"এপ্‌টোক নিয়েৰ ফিল্ড কমিউনিকেশ্বন (NFC) টেগ, কাৰ্ড আৰু ৰিডাৰসমূহৰ সৈতে যোগাযোগ কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"আপোনাৰ স্ক্ৰীন লক অক্ষম কৰক"</string>
- <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"এপটোক কী ল\'ক আৰু জড়িত হোৱা যিকোনো পাছৱৰ্ডৰ সুৰক্ষা অক্ষম কৰিব দিয়ে৷ উদাহৰণস্বৰূপে, কোনো অন্তৰ্গামী ফ\'ন কল উঠোৱাৰ সময়ত ফ\'নটোৱে কী-লকটো অক্ষম কৰে, তাৰ পিছত কল শেষ হ\'লেই কী লকটো পুনৰ সক্ষম কৰে৷"</string>
+ <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"এপ্‌টোক কী ল\'ক আৰু জড়িত হোৱা যিকোনো পাছৱৰ্ডৰ সুৰক্ষা অক্ষম কৰিব দিয়ে৷ উদাহৰণস্বৰূপে, কোনো অন্তৰ্গামী ফ\'ন কল উঠোৱাৰ সময়ত ফ\'নটোৱে কী-লকটো অক্ষম কৰে, তাৰ পাছত কল শেষ হ\'লেই কী লকটো পুনৰ সক্ষম কৰে৷"</string>
<string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"স্ক্ৰীন লকৰ জটিলতাৰ অনুৰোধ"</string>
<string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"এপ্‌টোক স্ক্ৰীন লকৰ জটিলতাৰ স্তৰ (উচ্চ, মধ্যম, নিম্ন বা একেবাৰে নাই)ৰ বিষয়ে জানিবলৈ অনুমতি দিয়ে, যিয়ে স্ক্ৰীন লকৰ সম্ভাব্য দৈৰ্ঘ্য বা স্ক্ৰীন লকৰ প্ৰকাৰ দৰ্শায়। লগতে এপ্‌টোৱে ব্যৱহাৰকাৰীক স্ক্ৰীন লকটো এটা নিৰ্দিষ্ট স্তৰলৈ আপডে’ট কৰিবলৈ পৰামৰ্শ দিব পাৰে যিটো ব্যৱহাৰকাৰীয়ে অৱজ্ঞা কৰি পৰৱর্তী পৃষ্ঠালৈ যাব পাৰে। মনত ৰাখিব যে স্ক্ৰীন লকটো সাধাৰণ পাঠ হিচাপে ষ্ট\'ৰ কৰা নহয়; সেয়েহে, এপ্‌টোৱে সঠিক পাছৱৰ্ডটো জানিব নোৱাৰে।"</string>
<string name="permlab_postNotification" msgid="4875401198597803658">"জাননী দেখুৱাওক"</string>
@@ -561,9 +561,9 @@
<string name="permlab_useBiometric" msgid="6314741124749633786">"বায়োমেট্ৰিক হাৰ্ডৱেৰ ব্যৱহাৰ কৰক"</string>
<string name="permdesc_useBiometric" msgid="7502858732677143410">"বিশ্বাসযোগ্য়তা প্ৰমাণীকৰণৰ বাবে এপক বায়োমেট্ৰিক হাৰ্ডৱেৰ ব্যৱহাৰ কৰিবলৈ অনুমতি দিয়ে"</string>
<string name="permlab_manageFingerprint" msgid="7432667156322821178">"ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ পৰিচালনা কৰিব পাৰে"</string>
- <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"ফিংগাৰপ্ৰিণ্ট টেম্প্লেটসমূহ যোগ কৰা বা মচাৰ পদ্ধতিসমূহ কামত লগাবলৈ নিৰ্দেশ দিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"ফিংগাৰপ্ৰিণ্ট টেম্প্লেটসমূহ যোগ কৰা বা মচাৰ পদ্ধতিসমূহ কামত লগাবলৈ নিৰ্দেশ দিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_useFingerprint" msgid="1001421069766751922">"ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ ব্যৱহাৰ কৰিব পাৰে"</string>
- <string name="permdesc_useFingerprint" msgid="412463055059323742">"প্ৰমাণীকৰণৰ বাবে ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ ব্যৱহাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে"</string>
+ <string name="permdesc_useFingerprint" msgid="412463055059323742">"প্ৰমাণীকৰণৰ বাবে ফিংগাৰপ্ৰিণ্ট হাৰ্ডৱেৰ ব্যৱহাৰ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে"</string>
<string name="permlab_audioWrite" msgid="8501705294265669405">"আপোনাৰ সংগীত সংগ্ৰহ সালসলনি কৰিবলৈ"</string>
<string name="permdesc_audioWrite" msgid="8057399517013412431">"এপক আপোনাৰ সংগীত সংগ্ৰহ সালসলনি কৰিবলৈ দিয়ে।"</string>
<string name="permlab_videoWrite" msgid="5940738769586451318">"আপোনাৰ ভিডিঅ’ সংগ্ৰহ সালসলনি কৰিবলৈ"</string>
@@ -689,7 +689,7 @@
<string name="permlab_readSyncSettings" msgid="6250532864893156277">"ছিংকৰ ছেটিং পঢ়ক"</string>
<string name="permdesc_readSyncSettings" msgid="1325658466358779298">"একাউণ্টৰ ছিংক ছেটিংবোৰ পঢ়িবলৈ এপক অনুমতি দিয়ে। যেনে, People এপ্‌টো কোনো একাউণ্টৰ সৈতে ছিংক কৰা হৈছে নে নাই সেয়া নির্ধাৰণ কৰিব পাৰে।"</string>
<string name="permlab_writeSyncSettings" msgid="6583154300780427399">"ছিংকক অন আৰু অফ ট\'গল কৰক"</string>
- <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"এপটোক কোনো একাউণ্টৰ ছিংক সম্পৰ্কীয় ছেটিংসমূহ সংশোধন কৰিবলৈ অনুমতি দিয়ে৷ উদাহৰণস্বৰূপে, এই কাৰ্যক কোনো একাউণ্টৰ জৰিয়তে People এপটোৰ ছিংক সক্ষম কৰিবলৈ ব্যৱহাৰ কৰিব পাৰি৷"</string>
+ <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"এপ্‌টোক কোনো একাউণ্টৰ ছিংক সম্পৰ্কীয় ছেটিংসমূহ সংশোধন কৰিবলৈ অনুমতি দিয়ে৷ উদাহৰণস্বৰূপে, এই কাৰ্যক কোনো একাউণ্টৰ জৰিয়তে People এপ্‌টোৰ ছিংক সক্ষম কৰিবলৈ ব্যৱহাৰ কৰিব পাৰি৷"</string>
<string name="permlab_readSyncStats" msgid="3747407238320105332">"ছিংকৰ পৰিসংখ্যা পঢ়ক"</string>
<string name="permdesc_readSyncStats" msgid="3867809926567379434">"ছিংকৰ কাৰ্যক্ৰমসমূহৰ ইতিহাস আৰু ছিংক কৰা ডেটাৰ পৰিমাণসহ কোনো একাউণ্টৰ ছিংকৰ তথ্য পঢ়িবলৈ এপক অনুমতি দিয়ে।"</string>
<string name="permlab_sdcardRead" msgid="5791467020950064920">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল পঢ়িব পাৰে"</string>
@@ -703,23 +703,23 @@
<string name="permlab_sdcardWrite" msgid="4863021819671416668">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল সংশোধন কৰিব বা মচিব পাৰে"</string>
<string name="permdesc_sdcardWrite" msgid="8376047679331387102">"আপোনাৰ শ্বেয়াৰ কৰি ৰখা ষ্ট’ৰেজৰ সমল লিখিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_use_sip" msgid="8250774565189337477">"SIP কল কৰা/পোৱা"</string>
- <string name="permdesc_use_sip" msgid="3590270893253204451">"এপটোক SIP কলসমূহ কৰিবলৈ আৰু পাবলৈ অনুমতি দিয়ে।"</string>
+ <string name="permdesc_use_sip" msgid="3590270893253204451">"এপ্‌টোক SIP কলসমূহ কৰিবলৈ আৰু পাবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_register_sim_subscription" msgid="1653054249287576161">"নতুন টেলিকম ছিম সংযোগসমূহ পঞ্জীয়ন কৰা"</string>
- <string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"এপটোক নতুন টেলিকম সংযোগ পঞ্জীয়ন কৰিবলৈ অনুমতি দিয়ে।"</string>
+ <string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"এপ্‌টোক নতুন টেলিকম সংযোগ পঞ্জীয়ন কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_register_call_provider" msgid="6135073566140050702">"নতুন টেলিকম সংযোগসমূহ পঞ্জীয়ন কৰা"</string>
- <string name="permdesc_register_call_provider" msgid="4201429251459068613">"এপটোক নতুন টেলিকম সংযোগ পঞ্জীয়ন কৰিবলৈ অনুমতি দিয়ে।"</string>
+ <string name="permdesc_register_call_provider" msgid="4201429251459068613">"এপ্‌টোক নতুন টেলিকম সংযোগ পঞ্জীয়ন কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_connection_manager" msgid="3179365584691166915">"টেলিকম সংযোগ পৰিচালনা কৰা"</string>
- <string name="permdesc_connection_manager" msgid="1426093604238937733">"এপটোক টেলিকম সংযোগ পৰিচালনা কৰিবলৈ অনুমতি দিয়ে।"</string>
+ <string name="permdesc_connection_manager" msgid="1426093604238937733">"এপ্‌টোক টেলিকম সংযোগ পৰিচালনা কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_bind_incall_service" msgid="5990625112603493016">"ইন-কল স্ক্ৰীনৰ সৈতে সংযোগ স্থাপন"</string>
<string name="permdesc_bind_incall_service" msgid="4124917526967765162">"ব্যৱহাৰকাৰীগৰাকীয়ে কেতিয়া আৰু কেনেদৰে ইন-কল-স্ক্ৰীন চায় সেয়া নিয়ন্ত্ৰণ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_bind_connection_service" msgid="5409268245525024736">"টেলিফ\'নী সেৱাসমূহৰ সৈতে সংযোগ স্থাপন"</string>
<string name="permdesc_bind_connection_service" msgid="6261796725253264518">"কল কৰিবলৈ/লাভ কৰিবলৈ টেলিফ\'নী সেৱাসমূহৰ সৈতে এপক সংযোগ স্থাপনৰ বাবে অনুমতি দিয়ে।"</string>
<string name="permlab_control_incall_experience" msgid="6436863486094352987">"ইন-কল ব্যৱহাৰকাৰীৰ অভিজ্ঞতা প্ৰদান কৰা"</string>
- <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"এপটোক ইন-কল ব্যৱহাৰকাৰীৰ অভিজ্ঞতা প্ৰদান কৰিবলৈ অনুমতি দিয়ে।"</string>
+ <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"এপ্‌টোক ইন-কল ব্যৱহাৰকাৰীৰ অভিজ্ঞতা প্ৰদান কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"নেটৱর্কৰ পূৰ্বতে হোৱা ব্যৱহাৰৰ বিষয়ে পঢ়ক"</string>
- <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"এপটোক বিশেষ নেটৱৰ্কবিলাকৰ আৰু এপ্‌সমূহৰ নেটৱৰ্ক ব্যৱহাৰৰ ইতিহাস পঢ়িবলৈ অনুমতি দিয়ে।"</string>
+ <string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"এপ্‌টোক বিশেষ নেটৱৰ্কবিলাকৰ আৰু এপ্‌সমূহৰ নেটৱৰ্ক ব্যৱহাৰৰ ইতিহাস পঢ়িবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"নেটৱর্কৰ নীতি পৰিচালনা কৰক"</string>
- <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"এপটোক নেটৱৰ্ক সংযোগৰ নীতিসমূহ পৰিচালনা কৰিবলৈ আৰু এপ্-বিশেষ নিয়ম সংজ্ঞাবদ্ধ কৰিবলৈ অনুমতি দিয়ে।"</string>
+ <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"এপ্‌টোক নেটৱৰ্ক সংযোগৰ নীতিসমূহ পৰিচালনা কৰিবলৈ আৰু এপ্-বিশেষ নিয়ম সংজ্ঞাবদ্ধ কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"নেটৱর্ক ব্যৱহাৰৰ হিচাপ সলনি কৰক"</string>
<string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"এপ অনুসুৰি নেটৱর্কৰ ব্যৱহাৰৰ হিচাপ সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। এয়া সাধাৰণ এপবোৰৰ ব্যৱহাৰৰ বাবে নহয়।"</string>
<string name="permlab_accessNotifications" msgid="7130360248191984741">"জাননীসমূহ এক্সেছ কৰে"</string>
@@ -747,7 +747,7 @@
<string name="permlab_bindCarrierServices" msgid="2395596978626237474">"বাহক সেৱাসমূহৰ সৈতে সংযুক্ত হ\'ব পাৰে"</string>
<string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"বাহক সেৱাৰ সৈতে সংযুক্ত হ\'বলৈ ধাৰকক অনুমতি দিয়ে। সাধাৰণ এপসমূহৰ বাবে সাধাৰণতে প্ৰয়োজন হ\'ব নালাগে।"</string>
<string name="permlab_access_notification_policy" msgid="5524112842876975537">"অসুবিধা নিদিব চাব পাৰে"</string>
- <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"অসুবিধা নিদিবৰ কনফিগাৰেশ্বনক পঢ়িবলৈ আৰু সালসলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"অসুবিধা নিদিবৰ কনফিগাৰেশ্বনক পঢ়িবলৈ আৰু সালসলনি কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"চোৱাৰ অনুমতিৰ ব্যৱহাৰ আৰম্ভ কৰক"</string>
<string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ধাৰকক কোনো এপৰ বাবে অনুমতিৰ ব্যৱহাৰ আৰম্ভ কৰিবলৈ দিয়ে। সাধাৰণ এপ্‌সমূহৰ বাবে কেতিয়াও প্ৰয়োজন হ’ব নালাগে।"</string>
<string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"অনুমতিৰ সিদ্ধান্তসমূহ চোৱা আৰম্ভ কৰক"</string>
@@ -914,7 +914,7 @@
<string name="keyguard_password_enter_password_code" msgid="2751130557661643482">"আনলক কৰিবলৈ পাছৱৰ্ড লিখক"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"আনলক কৰিবলৈ পিন লিখক"</string>
<string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"ভুল পিন ক\'ড।"</string>
- <string name="keyguard_label_text" msgid="3841953694564168384">"আনলক কৰিবলৈ মেনু টিপাৰ পিছত ০ টিপক।"</string>
+ <string name="keyguard_label_text" msgid="3841953694564168384">"আনলক কৰিবলৈ মেনু টিপাৰ পাছত ০ টিপক।"</string>
<string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"জৰুৰীকালীন নম্বৰ"</string>
<string name="lockscreen_carrier_default" msgid="6192313772955399160">"কোনো সেৱা নাই"</string>
<string name="lockscreen_screen_locked" msgid="7364905540516041817">"স্ক্ৰীন লক কৰা হ’ল।"</string>
@@ -949,12 +949,12 @@
<string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"ব্যৱহাৰকাৰীৰ নিৰ্দেশনা চাওক বা গ্ৰাহক সেৱা কেন্দ্ৰৰ সৈতে যোগাযোগ কৰক।"</string>
<string name="lockscreen_sim_locked_message" msgid="3160196135801185938">"ছিম কাৰ্ড লক কৰা হৈছে।"</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="2286497117428409709">"ছিম কার্ড আনলক কৰি থকা হৈছে…"</string>
- <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"আপুনি অশুদ্ধভাৱে আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিছে। \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক।"</string>
- <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"আপুনি অশুদ্ধভাৱে আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক।"</string>
- <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"আপুনি অশুদ্ধভাৱে আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক।"</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পিছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক৷"</string>
+ <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"আপুনি অশুদ্ধভাৱে আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিছে। \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"আপুনি অশুদ্ধভাৱে আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"আপুনি অশুদ্ধভাৱে আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পাছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক৷"</string>
<string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"আপুনি নিজৰ আনলক আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ দিলে। আকৌ <xliff:g id="NUMBER_1">%2$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাক নিজৰ Google ছাইন ইন ব্যৱহাৰ কৰি আপোনাৰ Android TV ডিভাইচটো আনলক কৰিবলৈ কোৱা হ’ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g>ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পিছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত পুনৰ চেষ্টা কৰক৷"</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"আপুনি অশুদ্ধভাৱে আপোনাৰ লক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ আঁকিলে৷ <xliff:g id="NUMBER_1">%2$d</xliff:g> তকৈ অধিকবাৰ অসফলভাৱে কৰা প্ৰয়াসৰ পাছত, আপোনাৰ ফ\'নটো আনলক কৰিবৰ বাবে Google ছাইন ইনৰ জৰিয়তে কাৰ্যটো কৰিবলৈ কোৱা হ\'ব৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক৷"</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"আপুনি টে\'বলেটটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আনলক কৰিবলৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g> বাৰতকৈ বেছি প্ৰয়াস কৰিলে টে\'বলেটটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু আটাইবোৰ ব্যৱহাৰকাৰী ডেটা হেৰুৱাব।"</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাৰ Android TV ডিভাইচটো ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব আৰু ব্যৱহাৰকাৰীৰ সকলো ডেটা হেৰুৱাব।"</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"আপুনি ফ\'নটো <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আনলক কৰিবলৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g> বাৰতকৈ বেছি প্ৰয়াস কৰিলে ফ\'নটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু আটাইবোৰ ব্যৱহাৰকাৰী ডেটা হেৰুৱাব।"</string>
@@ -1049,11 +1049,11 @@
<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>
+ <string name="permdesc_setAlarm" msgid="2185033720060109640">"এপ্‌টোক ইনষ্টল হৈ থকা এলাৰ্ম ক্লক এপত এলাৰ্ম ছেট কৰিবলৈ অনুমতি দিয়ে। কিছুমান এলাৰ্ম ক্লক এপত এই সুবিধাটো প্ৰযোজ্য নহ’ব পাৰে।"</string>
<string name="permlab_addVoicemail" msgid="4770245808840814471">"ভইচমেইল যোগ কৰক"</string>
- <string name="permdesc_addVoicemail" msgid="5470312139820074324">"আপোনাৰ ভইচমেইল ইনবক্সত বাৰ্তাবোৰ যোগ কৰিবলৈ এপটোক অনুমতি দিয়ক।"</string>
+ <string name="permdesc_addVoicemail" msgid="5470312139820074324">"আপোনাৰ ভইচমেইল ইনবক্সত বাৰ্তাবোৰ যোগ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ক।"</string>
<string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"ব্ৰাউজাৰৰ জিঅ\'লোকেশ্বনৰ অনুমতিসমূহ সংশোধন কৰক"</string>
- <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"ব্ৰাউজাৰৰ জিঅ\'লোকেশ্বন বিষয়ক অনুমতিসমূহ সংশোধন কৰিবলৈ এপটোক অনুমতি দিয়ে৷ ক্ষতিকাৰক এপবোৰে একপক্ষীয় ৱেবছাইটসমূহলৈ অৱস্থান সেৱাৰ তথ্য পঠিয়াবলৈ ইয়াক ব্যৱহাৰ কৰিব পাৰে৷"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"ব্ৰাউজাৰৰ জিঅ\'লোকেশ্বন বিষয়ক অনুমতিসমূহ সংশোধন কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে৷ ক্ষতিকাৰক এপবোৰে একপক্ষীয় ৱেবছাইটসমূহলৈ অৱস্থান সেৱাৰ তথ্য পঠিয়াবলৈ ইয়াক ব্যৱহাৰ কৰিব পাৰে৷"</string>
<string name="save_password_message" msgid="2146409467245462965">"ব্ৰাউজাৰে এই পাছৱর্ডটো মনত ৰখাটো বিচাৰেনে?"</string>
<string name="save_password_notnow" msgid="2878327088951240061">"এতিয়াই নহয়"</string>
<string name="save_password_remember" msgid="6490888932657708341">"মনত ৰাখিব"</string>
@@ -1208,12 +1208,12 @@
<string name="aerr_process" msgid="4268018696970966407">"<xliff:g id="PROCESS">%1$s</xliff:g> বন্ধ হ’ল"</string>
<string name="aerr_application_repeated" msgid="7804378743218496566">"<xliff:g id="APPLICATION">%1$s</xliff:g> বাৰে বাৰে বন্ধ হৈ গৈছে"</string>
<string name="aerr_process_repeated" msgid="1153152413537954974">"<xliff:g id="PROCESS">%1$s</xliff:g> বাৰে বাৰে বন্ধ হৈ গৈছে"</string>
- <string name="aerr_restart" msgid="2789618625210505419">"আকৌ এপটো খোলক"</string>
+ <string name="aerr_restart" msgid="2789618625210505419">"আকৌ এপ্‌টো খোলক"</string>
<string name="aerr_report" msgid="3095644466849299308">"আপোনাৰ প্ৰতিক্ৰিয়া পঠিয়াওক"</string>
<string name="aerr_close" msgid="3398336821267021852">"বন্ধ কৰক"</string>
<string name="aerr_mute" msgid="2304972923480211376">"ডিভাইচ ৰিষ্টাৰ্ট নোহোৱালৈ মিউট কৰক"</string>
<string name="aerr_wait" msgid="3198677780474548217">"অপেক্ষা কৰক"</string>
- <string name="aerr_close_app" msgid="8318883106083050970">"এপটো বন্ধ কৰক"</string>
+ <string name="aerr_close_app" msgid="8318883106083050970">"এপ্‌টো বন্ধ কৰক"</string>
<string name="anr_title" msgid="7290329487067300120"></string>
<string name="anr_activity_application" msgid="8121716632960340680">"<xliff:g id="APPLICATION">%2$s</xliff:g>য়ে সঁহাৰি দিয়া নাই"</string>
<string name="anr_activity_process" msgid="3477362583767128667">"<xliff:g id="ACTIVITY">%1$s</xliff:g>য়ে সঁহাৰি দিয়া নাই"</string>
@@ -1231,7 +1231,7 @@
<string name="screen_compat_mode_hint" msgid="4032272159093750908">"ছিষ্টেমৰ ছেটিং &gt; এপ্‌ &gt; ডাউনল’ড কৰা সমল-লৈ গৈ ইয়াক আকৌ সক্ষম কৰক।"</string>
<string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ বর্তমানৰ ডিছপ্লে’ৰ আকাৰ ছেটিং ব্যৱহাৰ কৰিব নোৱাৰে আৰু ই সঠিকভাৱে নচলিবও পাৰে।"</string>
<string name="unsupported_display_size_show" msgid="980129850974919375">"সদায় দেখুৱাওক"</string>
- <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g>ক এটা খাপ নোখোৱা Android OS সংস্কৰণৰ বাবে তৈয়াৰ কৰা হৈছিল, যাৰ ফলত ই অস্বাভাৱিকধৰণে আচৰণ কৰিব পাৰে। এপটোৰ শেহতীয়া সংস্কৰণ উপলব্ধ হ\'ব পাৰে।"</string>
+ <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g>ক এটা খাপ নোখোৱা Android OS সংস্কৰণৰ বাবে তৈয়াৰ কৰা হৈছিল, যাৰ ফলত ই অস্বাভাৱিকধৰণে আচৰণ কৰিব পাৰে। এপ্‌টোৰ শেহতীয়া সংস্কৰণ উপলব্ধ হ\'ব পাৰে।"</string>
<string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"সদায় দেখুৱাওক"</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>) নিজে বলবৎ কৰা StrictMode নীতি ভংগ কৰিলে।"</string>
@@ -1328,7 +1328,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">"ছিম কাৰ্ড আঁতৰোৱা হ’ল"</string>
@@ -1338,8 +1338,8 @@
<string name="sim_added_message" msgid="6602906609509958680">"ম\'বাইলৰ নেটৱর্ক ব্যৱহাৰ কৰিবলৈ আপোনাৰ ডিভাইচটো ৰিষ্টার্ট কৰক।"</string>
<string name="sim_restart_button" msgid="8481803851341190038">"ৰিষ্টাৰ্ট কৰক"</string>
<string name="install_carrier_app_notification_title" msgid="5712723402213090102">"ম’বাইল সেৱা সক্ৰিয় কৰক"</string>
- <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"আপোনাৰ নতুন ছিমখন সক্ৰিয় কৰিবলৈ বাহকৰ এপটো ডাউনল’ড কৰক"</string>
- <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"আপোনাৰ নতুন ছিমখন সক্ৰিয় কৰিবলৈ <xliff:g id="APP_NAME">%1$s</xliff:g> এপটো ডাউনল’ড কৰক"</string>
+ <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"আপোনাৰ নতুন ছিমখন সক্ৰিয় কৰিবলৈ বাহকৰ এপ্‌টো ডাউনল’ড কৰক"</string>
+ <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"আপোনাৰ নতুন ছিমখন সক্ৰিয় কৰিবলৈ <xliff:g id="APP_NAME">%1$s</xliff:g> এপ্‌টো ডাউনল’ড কৰক"</string>
<string name="install_carrier_app_notification_button" msgid="6257740533102594290">"এপ্ ডাউনল’ড কৰক"</string>
<string name="carrier_app_notification_title" msgid="5815477368072060250">"নতুন ছিম ভৰোৱা হৈছে"</string>
<string name="carrier_app_notification_text" msgid="6567057546341958637">"ছেট আপ কৰিবলৈ টিপক"</string>
@@ -1452,9 +1452,9 @@
<string name="permlab_readInstallSessions" msgid="7279049337895583621">"ইনষ্টল কৰা ছেশ্বনসমূহ পঢ়িব পাৰে"</string>
<string name="permdesc_readInstallSessions" msgid="4012608316610763473">"এটা এপ্লিকেশ্বনক ইনষ্টল কৰা ছেশ্বনসমূহ পঢ়িবলৈ অনুমতি দিয়ে। এই কাৰ্যই সক্ৰিয় পেকেজ ইনষ্টলেশ্বনৰ বিষয়ে চাবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_requestInstallPackages" msgid="7600020863445351154">"পেকেজ ইনষ্টলৰ বাবে অনুৰোধ কৰিব পাৰে"</string>
- <string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"পেকেজ ইনষ্টল কৰাৰ অনুৰোধ প্ৰেৰণ কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string>
+ <string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"পেকেজ ইনষ্টল কৰাৰ অনুৰোধ প্ৰেৰণ কৰিবলৈ এপ্‌টোক অনুমতি দিয়ে।"</string>
<string name="permlab_requestDeletePackages" msgid="2541172829260106795">"পেকেজ মচাৰ অনুৰোধ কৰিব পাৰে"</string>
- <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"এপটোক পেকেজবোৰ মচাৰ অনুৰোধ কৰিবলৈ দিয়ে।"</string>
+ <string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"এপ্‌টোক পেকেজবোৰ মচাৰ অনুৰোধ কৰিবলৈ দিয়ে।"</string>
<string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"বেটাৰী অপ্টিমাইজেশ্বন উপেক্ষা কৰিবলৈ বিচাৰক"</string>
<string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"কোনো এপক সেই এপ্‌টোৰ বাবে বেটাৰী অপ্টিমাইজেশ্বন উপেক্ষা কৰিবলৈ অনুমতি বিচাৰিবলৈ দিয়ে।"</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"আটাইবোৰ পেকেজত প্ৰশ্ন সোধক"</string>
@@ -1478,8 +1478,8 @@
<string name="permission_request_notification_title" msgid="1810025922441048273">"অনুমতি বিচাৰি অনুৰোধ কৰা হৈছে"</string>
<string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"<xliff:g id="ACCOUNT">%s</xliff:g> একাউণ্টৰ বাবে\nঅনুমতি বিচাৰি অনুৰোধ কৰা হৈছে।"</string>
<string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"<xliff:g id="APP">%1$s</xliff:g>এ <xliff:g id="ACCOUNT">%2$s</xliff:g> একাউণ্টটো এক্সেছৰ \nঅনুমতি বিচাৰি অনুৰোধ জনাইছে।"</string>
- <string name="forward_intent_to_owner" msgid="4620359037192871015">"আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ বাহিৰত এই এপটো ব্যৱহাৰ কৰি আছে"</string>
- <string name="forward_intent_to_work" msgid="3620262405636021151">"আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ ভিতৰত এই এপটো ব্যৱহাৰ কৰি আছে"</string>
+ <string name="forward_intent_to_owner" msgid="4620359037192871015">"আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ বাহিৰত এই এপ্‌টো ব্যৱহাৰ কৰি আছে"</string>
+ <string name="forward_intent_to_work" msgid="3620262405636021151">"আপুনি আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইলৰ ভিতৰত এই এপ্‌টো ব্যৱহাৰ কৰি আছে"</string>
<string name="input_method_binding_label" msgid="1166731601721983656">"ইনপুট পদ্ধতি"</string>
<string name="sync_binding_label" msgid="469249309424662147">"ছিংক"</string>
<string name="accessibility_binding_label" msgid="1974602776545801715">"সাধ্য সুবিধাসমূহ"</string>
@@ -1560,7 +1560,7 @@
<string name="shareactionprovider_share_with" msgid="2753089758467748982">"ইয়াৰ জৰিয়তে শ্বেয়াৰ কৰক"</string>
<string name="shareactionprovider_share_with_application" msgid="4902832247173666973">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>ৰ জৰিয়তে শ্বেয়াৰ কৰক"</string>
<string name="content_description_sliding_handle" msgid="982510275422590757">"শ্লাইড কৰা হেণ্ডেল৷ স্পৰ্শ কৰক আৰু ধৰি ৰাখক৷"</string>
- <string name="description_target_unlock_tablet" msgid="7431571180065859551">"স্ক্ৰীণ আনলক কৰিবলৈ ছোৱাইপ কৰক৷"</string>
+ <string name="description_target_unlock_tablet" msgid="7431571180065859551">"স্ক্ৰীন আনলক কৰিবলৈ ছোৱাইপ কৰক৷"</string>
<string name="action_bar_home_description" msgid="1501655419158631974">"গৃহ পৃষ্ঠালৈ যাওক"</string>
<string name="action_bar_up_description" msgid="6611579697195026932">"ওপৰলৈ যাওক"</string>
<string name="action_menu_overflow_description" msgid="4579536843510088170">"অধিক বিকল্প"</string>
@@ -1660,18 +1660,18 @@
<string name="kg_login_invalid_input" msgid="8292367491901220210">"ব্যৱহাৰকাৰীৰ অমান্য নাম বা পাছৱর্ড।"</string>
<string name="kg_login_account_recovery_hint" msgid="4892466171043541248">"নিজৰ ব্যৱহাৰকাৰী নাম আৰু পাছৱর্ড পাহৰিলেনে?\n"<b>"google.com/accounts/recovery"</b>" লৈ যাওক।"</string>
<string name="kg_login_checking_password" msgid="4676010303243317253">"একাউণ্ট পৰীক্ষা কৰি থকা হৈছে…"</string>
- <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
- <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"আপুনি আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
- <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
+ <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="23741434207544038">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
+ <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"আপুনি আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
+ <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"আপুনি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ টেবলেটৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল প্ৰয়াস কৰিলে টেবলেটটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু আটাইবোৰ ব্যৱহাৰকাৰীৰ ডেটা হেৰুৱাব।"</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাৰ Android TV ডিভাইচটো ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব আৰু ব্যৱহাৰকাৰীৰ আটাইবোৰ ডেটা হেৰুৱাব।"</string>
<string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"আপুনি <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ ফ\'নৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল প্ৰয়াস কৰিলে ফ\'নটো ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব আৰু ব্যৱহাৰকাৰীৰ আটাইবোৰ ডেটা হেৰুৱাব।"</string>
<string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"আপুনি <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ টেবলেটৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। টেবলেটটো এতিয়া ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব।"</string>
<string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"আপুনি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰিছে। আপোনাৰ Android TV ডিভাইচটো এতিয়া ফেক্টৰী ডিফ’ল্টলৈ ৰিছেট কৰা হ’ব।"</string>
<string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"আপুনি <xliff:g id="NUMBER">%d</xliff:g>বাৰ ভুলকৈ ফ\'নৰ ল\'ক খোলাৰ প্ৰয়াস কৰিছে। ফ\'নটো এতিয়া ফেক্টৰী ডিফ\'ল্টলৈ ৰিছেট কৰা হ\'ব।"</string>
- <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ টেবলেটটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ টেবলেটটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
<string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"আপুনি নিজৰ আনলক আর্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ দিয়ে। আকৌ <xliff:g id="NUMBER_1">%2$d</xliff:g>বাৰ ভুলকৈ প্ৰয়াস কৰাৰ পাছত আপোনাক এটা ইমেইল একাউণ্ট ব্যৱহাৰ কৰি নিজৰ Android TV ডিভাইচটো আনলক কৰিবলৈ কোৱা হ’ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g>ছেকেণ্ডৰ পাছত পুনৰ চেষ্টা কৰক।"</string>
- <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ ফ\'নটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
+ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ ফ\'নটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
<string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
<string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"আঁতৰাওক"</string>
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"অনুমোদিত স্তৰতকৈ ওপৰলৈ ভলিউম বঢ়াব নেকি?\n\nদীৰ্ঘ সময়ৰ বাবে উচ্চ ভলিউমত শুনাৰ ফলত শ্ৰৱণ ক্ষমতাৰ ক্ষতি হ\'ব পাৰে।"</string>
@@ -1835,7 +1835,7 @@
<string name="restr_pin_create_pin" msgid="917067613896366033">"সীমাবদ্ধতা সংশোধন কৰিবলৈ এটা পিন সৃষ্টি কৰক"</string>
<string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"পিনবোৰ মিলা নাই। আকৌ চেষ্টা কৰক।"</string>
<string name="restr_pin_error_too_short" msgid="1547007808237941065">"পিনটো অতি চুটি। কমেও ৪টা সংখ্যাৰ হ\'ব লাগিব।"</string>
- <string name="restr_pin_try_later" msgid="5897719962541636727">"পিছত আকৌ চেষ্টা কৰক"</string>
+ <string name="restr_pin_try_later" msgid="5897719962541636727">"পাছত আকৌ চেষ্টা কৰক"</string>
<string name="immersive_cling_title" msgid="2307034298721541791">"স্ক্ৰীন পূৰ্ণৰূপত চাই আছে"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"বাহিৰ হ\'বলৈ ওপৰৰপৰা তললৈ ছোৱাইপ কৰক।"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"বুজি পালোঁ"</string>
@@ -1931,7 +1931,7 @@
<string name="language_picker_section_all" msgid="1985809075777564284">"সকলো ভাষা"</string>
<string name="region_picker_section_all" msgid="756441309928774155">"আটাইবোৰ অঞ্চল"</string>
<string name="locale_search_menu" msgid="6258090710176422934">"সন্ধান কৰক"</string>
- <string name="app_suspended_title" msgid="888873445010322650">"এপটো নাই"</string>
+ <string name="app_suspended_title" msgid="888873445010322650">"এপ্‌টো নাই"</string>
<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>
@@ -1958,7 +1958,7 @@
<string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"এইটো আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ত এক্সেছ কৰিব নোৱাৰি। তাৰ পৰিৱৰ্তে আপোনাৰ Android TV ডিভাইচত চেষ্টা কৰি চাওক।"</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"এইটো আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ত এক্সেছ কৰিব নোৱাৰি। তাৰ পৰিৱৰ্তে আপোনাৰ টেবলেটত চেষ্টা কৰি চাওক।"</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"এইটো আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ত এক্সেছ কৰিব নোৱাৰি। তাৰ পৰিৱৰ্তে আপোনাৰ ফ’নত চেষ্টা কৰি চাওক।"</string>
- <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই এপটো Androidৰ এটা পুৰণা সংস্কৰণৰ বাবে প্ৰস্তুত কৰা হৈছিল, আৰু ই বিচৰাধৰণে কাম নকৰিবও পাৰে। ইয়াৰ আপডে’ট আছে নেকি চাওক, বা বিকাশকৰ্তাৰ সৈতে যোগাযোগ কৰক।"</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>
<string name="new_sms_notification_content" msgid="3197949934153460639">"চাবলৈ এছএমএছ এপ্ খোলক"</string>
@@ -1997,7 +1997,7 @@
<string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"সময়ৰ ইনপুটৰ বাবে পাঠৰ ইনপুট ম\'ডলৈ যাওক।"</string>
<string name="time_picker_radial_mode_description" msgid="1222342577115016953">"সময়ৰ ইনপুটৰ বাবে ঘড়ী ম\'ডলৈ যাওক।"</string>
<string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"স্বয়ংপূৰ্তিৰ বিকল্পসমূহ"</string>
- <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"পিছত স্বয়ংপূৰ্তি কৰিবলৈ ছেভ কৰক"</string>
+ <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"পাছত স্বয়ংপূৰ্তি কৰিবলৈ ছেভ কৰক"</string>
<string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"সমলসমূহ স্বয়ংপূৰ্তি কৰিব নোৱাৰি"</string>
<string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"কোনো স্বয়ংপূৰ্তি পৰামৰ্শ নাই"</string>
<string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{এটা স্বয়ংপূৰ্তি পৰামৰ্শ}one{# টা স্বয়ংপূৰ্তি পৰামৰ্শ}other{# টা স্বয়ংপূৰ্তি পৰামৰ্শ}}"</string>
@@ -2040,7 +2040,7 @@
<string name="popup_window_default_title" msgid="6907717596694826919">"পপআপ ৱিণ্ড\'"</string>
<string name="slice_more_content" msgid="3377367737876888459">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"এপৰ সংস্কৰণ অৱনমিত কৰা হৈছে, বা ই এই শ্বৰ্টকাটটোৰ লগত খাপ নাখায়"</string>
- <string name="shortcut_restore_not_supported" msgid="4763198938588468400">"এপটোত বেকআপ আৰু পুনঃস্থাপন সুবিধা নথকাৰ বাবে শ্বৰ্টকাট পুনঃস্থাপন কৰিবপৰা নগ\'ল"</string>
+ <string name="shortcut_restore_not_supported" msgid="4763198938588468400">"এপ্‌টোত বেকআপ আৰু পুনঃস্থাপন সুবিধা নথকাৰ বাবে শ্বৰ্টকাট পুনঃস্থাপন কৰিবপৰা নগ\'ল"</string>
<string name="shortcut_restore_signature_mismatch" msgid="579345304221605479">"এপৰ স্বাক্ষৰৰ অমিল হোৱাৰ বাবে শ্বৰ্টকাট পুনঃস্থাপন কৰিবপৰা নগ\'ল"</string>
<string name="shortcut_restore_unknown_issue" msgid="2478146134395982154">"শ্বৰ্টকাট পুনঃস্থাপন কৰিবপৰা নগ\'ল"</string>
<string name="shortcut_disabled_reason_unknown" msgid="753074793553599166">"শ্বৰ্টকাট অক্ষম কৰি থোৱা হৈছে"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index b52e57a57a4d..4f6d0c7f9d11 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1949,15 +1949,15 @@
<string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"La configuració d\'Android TV no està disponible"</string>
<string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"La configuració de la tauleta no està disponible"</string>
<string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Configuració del telèfon no disponible"</string>
- <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"En aquests moments, no es pot accedir a aquesta aplicació al dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string>
- <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"En aquests moments, no es pot accedir a aquesta aplicació al dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string>
- <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"No es pot accedir a aquesta aplicació al teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string>
+ <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"En aquests moments, No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string>
+ <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"En aquests moments, No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string>
+ <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho al dispositiu Android TV."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho a la tauleta."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Aquesta aplicació sol·licita seguretat addicional. Prova-ho al telèfon."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"No s\'hi pot accedir des del dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"No s\'hi pot accedir des del dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"No s\'hi pot accedir des del dispositiu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al dispositiu Android TV."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho a la tauleta."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"No s\'hi pot accedir des del teu <xliff:g id="DEVICE">%1$s</xliff:g>. Prova-ho al telèfon."</string>
<string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aquesta aplicació es va crear per a una versió antiga d\'Android i pot ser que no funcioni correctament. Prova de cercar actualitzacions o contacta amb el desenvolupador."</string>
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Cerca actualitzacions"</string>
<string name="new_sms_notification_title" msgid="6528758221319927107">"Tens missatges nous"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 2a3f916dcdec..e8e5a514d15f 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1970,8 +1970,8 @@
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Presiona para ver archivos"</string>
<string name="pin_target" msgid="8036028973110156895">"Fijar"</string>
<string name="pin_specific_target" msgid="7824671240625957415">"Fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
- <string name="unpin_target" msgid="3963318576590204447">"No fijar"</string>
- <string name="unpin_specific_target" msgid="3859828252160908146">"No fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="unpin_target" msgid="3963318576590204447">"Dejar de fijar"</string>
+ <string name="unpin_specific_target" msgid="3859828252160908146">"Dejar de fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="app_info" msgid="6113278084877079851">"Información de apps"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Iniciando demostración…"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 90f1006e6e50..fcbe2b8b62b3 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1956,9 +1956,9 @@
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguridad adicional. Prueba en tu dispositivo Android TV."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esta aplicación solicita seguridad adicional. Prueba en tu tablet."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esta aplicación solicita seguridad adicional. Prueba en tu teléfono."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"No se puede acceder a este contenido en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu dispositivo Android TV."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"No se puede acceder desde tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu dispositivo Android TV."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"No se puede acceder a este contenido en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu tablet."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"No se puede acceder a este contenido en tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu teléfono."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"No se puede acceder desde tu <xliff:g id="DEVICE">%1$s</xliff:g>. Prueba en tu teléfono."</string>
<string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación se ha diseñado para una versión anterior de Android y es posible que no funcione correctamente. Busca actualizaciones o ponte en contacto con el desarrollador."</string>
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualizaciones"</string>
<string name="new_sms_notification_title" msgid="6528758221319927107">"Tienes mensajes nuevos"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 15b25adf9ec7..681cc914383f 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -763,10 +763,10 @@
<string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Kontrolatu zenbat aldiz idatzi duzun oker pasahitza pantaila desblokeatzen saiatzean, eta blokeatu Android TV gailua edo ezabatu bertako datu guztiak pasahitza gehiegitan idazten baduzu oker."</string>
<string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Kontrolatu zenbatetan idazten duzun pasahitza oker pantaila desblokeatzen saiatzean eta, gehiegitan idazten bada oker, blokeatu informazio- eta aisia-sistema edo ezabatu hango eduki guztia."</string>
<string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu telefonoa edo ezabatu bere datuak pasahitza gehiegitan oker idazten bada."</string>
- <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu tableta edo ezabatu erabiltzailearen datuak pasahitza gehiegitan oker idazten bada."</string>
- <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Kontrolatu zenbat aldiz idatzi duzun oker pasahitza pantaila desblokeatzen saiatzean, eta blokeatu Android TV gailua edo ezabatu erabiltzailearen datuak pasahitza gehiegitan idazten baduzu oker."</string>
+ <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu tableta edo ezabatu erabiltzaile-datuak pasahitza gehiegitan oker idazten bada."</string>
+ <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Kontrolatu zenbat aldiz idatzi duzun oker pasahitza pantaila desblokeatzen saiatzean, eta blokeatu Android TV gailua edo ezabatu erabiltzaile-datuak pasahitza gehiegitan idazten baduzu oker."</string>
<string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Kontrolatu zenbatetan idazten duzun pasahitza oker pantaila desblokeatzen saiatzean eta, gehiegitan idazten bada oker, blokeatu informazio- eta aisia-sistema edo ezabatu profil honetako eduki guztia."</string>
- <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu telefonoa edo ezabatu erabiltzailearen datuak pasahitza gehiegitan oker idazten bada."</string>
+ <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Kontrolatu pantaila desblokeatzen saiatzean idatzitako pasahitz oker kopurua, eta blokeatu telefonoa edo ezabatu erabiltzaile-datuak pasahitza gehiegitan oker idazten bada."</string>
<string name="policylab_resetPassword" msgid="214556238645096520">"Aldatu pantailaren blokeoa"</string>
<string name="policydesc_resetPassword" msgid="4626419138439341851">"Aldatu pantailaren blokeoa."</string>
<string name="policylab_forceLock" msgid="7360335502968476434">"Blokeatu pantaila"</string>
@@ -777,7 +777,7 @@
<string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"Berrezarri informazio- eta aisia-sistemako jatorrizko datuak abisatu gabe, bertan zegoen eduki guztia ezabatzeko."</string>
<string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Ezabatu telefonoaren datuak abisatu gabe, jatorrizko datuak berrezarrita."</string>
<string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"Ezabatu profileko eduki guztia"</string>
- <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Ezabatu erabiltzailearen datuak"</string>
+ <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Ezabatu erabiltzaile-datuak"</string>
<string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"Ezabatu erabiltzaileak tabletan dituen datuak abisatu gabe."</string>
<string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Ezabatu erabiltzaileak Android TV gailuan dituen datuak abisatu gabe."</string>
<string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Ezabatu informazio- eta aisia-sisteman dagoen profil honetako eduki guztia abisatu gabe."</string>
@@ -1134,7 +1134,7 @@
<string name="Midnight" msgid="8176019203622191377">"Gauerdia"</string>
<string name="elapsed_time_short_format_mm_ss" msgid="8689459651807876423">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="2302144714803345056">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
- <string name="selectAll" msgid="1532369154488982046">"Hautatu guztiak"</string>
+ <string name="selectAll" msgid="1532369154488982046">"Hautatu dena"</string>
<string name="cut" msgid="2561199725874745819">"Ebaki"</string>
<string name="copy" msgid="5472512047143665218">"Kopiatu"</string>
<string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"Ezin izan da kopiatu arbelean"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 104808b1f8b9..b86ce04e5b24 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1403,7 +1403,7 @@
<string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Analyse de l\'espace de stockage sur le support en cours…"</string>
<string name="ext_media_new_notification_title" msgid="3517407571407687677">"Nouveau périphérique <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="ext_media_new_notification_title" product="automotive" msgid="9085349544984742727">"<xliff:g id="NAME">%s</xliff:g> ne fonctionne pas"</string>
- <string name="ext_media_new_notification_message" msgid="6095403121990786986">"Toucher pour configurer"</string>
+ <string name="ext_media_new_notification_message" msgid="6095403121990786986">"Touchez pour configurer"</string>
<string name="ext_media_new_notification_message" product="tv" msgid="216863352100263668">"Sélectionnez pour configurer"</string>
<string name="ext_media_new_notification_message" product="automotive" msgid="5140127881613227162">"Vous devrez peut-être reformater l\'appareil. Touchez pour l\'éjecter."</string>
<string name="ext_media_ready_notification_message" msgid="7509496364380197369">"Pour stocker des photos, des vidéos, de la musique et plus encore"</string>
@@ -1415,7 +1415,7 @@
<string name="ext_media_unmountable_notification_message" product="automotive" msgid="2274596120715020680">"Vous devrez peut-être reformater l\'appareil. Touchez pour l\'éjecter."</string>
<string name="ext_media_unsupported_notification_title" msgid="3487534182861251401">"<xliff:g id="NAME">%s</xliff:g> détecté"</string>
<string name="ext_media_unsupported_notification_title" product="automotive" msgid="6004193172658722381">"<xliff:g id="NAME">%s</xliff:g> ne fonctionne pas"</string>
- <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Toucher pour configurer ."</string>
+ <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Touchez pour configurer ."</string>
<string name="ext_media_unsupported_notification_message" product="tv" msgid="1595482802187036532">"Sélectionner pour configurer <xliff:g id="NAME">%s</xliff:g> dans un format pris en charge."</string>
<string name="ext_media_unsupported_notification_message" product="automotive" msgid="3412494732736336330">"Vous devrez peut-être reformater l\'appareil"</string>
<string name="ext_media_badremoval_notification_title" msgid="4114625551266196872">"Retrait inattendu de la mémoire « <xliff:g id="NAME">%s</xliff:g> »"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 7f099b056f82..f8e9efb58ecc 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -580,7 +580,7 @@
<string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Utilisez la biométrie ou le verrouillage de l\'écran pour continuer"</string>
<string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Matériel biométrique indisponible"</string>
<string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentification annulée"</string>
- <string name="biometric_not_recognized" msgid="5106687642694635888">"Non reconnu"</string>
+ <string name="biometric_not_recognized" msgid="5106687642694635888">"Non reconnue"</string>
<string name="biometric_error_canceled" msgid="8266582404844179778">"Authentification annulée"</string>
<string name="biometric_error_device_not_secured" msgid="3129845065043995924">"Aucun code, schéma ni mot de passe n\'est défini"</string>
<string name="biometric_error_generic" msgid="6784371929985434439">"Erreur d\'authentification"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 79faaac94919..0daa3249cc0c 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1139,7 +1139,7 @@
<string name="copy" msgid="5472512047143665218">"कॉपी करें"</string>
<string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"क्लिपबोर्ड पर कॉपी नहीं हो सका"</string>
<string name="paste" msgid="461843306215520225">"चिपकाएं"</string>
- <string name="paste_as_plain_text" msgid="7664800665823182587">"सादे पाठ के रूप में चिपकाएं"</string>
+ <string name="paste_as_plain_text" msgid="7664800665823182587">"सादे टेक्स्ट के रूप में चिपकाएं"</string>
<string name="replace" msgid="7842675434546657444">"बदलें•"</string>
<string name="delete" msgid="1514113991712129054">"मिटाएं"</string>
<string name="copyUrl" msgid="6229645005987260230">"यूआरएल को कॉपी करें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index fb95a1f1acca..73cbf9343bd4 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1210,7 +1210,7 @@
<string name="aerr_application_repeated" msgid="7804378743218496566">"Aplikacija <xliff:g id="APPLICATION">%1$s</xliff:g> neprekidno se ruši"</string>
<string name="aerr_process_repeated" msgid="1153152413537954974">"Postupak <xliff:g id="PROCESS">%1$s</xliff:g> neprekidno se ruši"</string>
<string name="aerr_restart" msgid="2789618625210505419">"Ponovo otvori aplikaciju"</string>
- <string name="aerr_report" msgid="3095644466849299308">"Pošalji povratne informacije"</string>
+ <string name="aerr_report" msgid="3095644466849299308">"Pošaljite povratne informacije"</string>
<string name="aerr_close" msgid="3398336821267021852">"Zatvori"</string>
<string name="aerr_mute" msgid="2304972923480211376">"Zanemari do ponovnog pokretanja uređaja"</string>
<string name="aerr_wait" msgid="3198677780474548217">"Čekaj"</string>
@@ -1951,14 +1951,14 @@
<string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"Postavke tableta nisu dostupne"</string>
<string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Postavke telefona nisu dostupne"</string>
<string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na Android TV uređaju."</string>
- <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na svojem tabletu."</string>
- <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na svojem telefonu."</string>
+ <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na tabletu."</string>
+ <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Trenutačno toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na telefonu."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Ta aplikacija zahtijeva dodatnu sigurnost. Pokušajte joj pristupiti na Android TV uređaju."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Ta aplikacija zahtijeva dodatnu sigurnost. Pokušajte joj pristupiti na tabletu."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Ta aplikacija zahtijeva dodatnu sigurnost. Pokušajte joj pristupiti na telefonu."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na Android TV uređaju."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na svojem tabletu."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na svojem telefonu."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na tabletu."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Toj aplikaciji nije moguće pristupiti na vašem uređaju <xliff:g id="DEVICE">%1$s</xliff:g>. Pokušajte joj pristupiti na telefonu."</string>
<string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova je aplikacija razvijena za stariju verziju Androida i možda neće funkcionirati pravilno. Potražite ažuriranja ili se obratite razvojnom programeru."</string>
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Provjeri ažuriranja"</string>
<string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 9ab64e6f26df..d11eca61752f 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -252,7 +252,7 @@
<string name="bugreport_message" msgid="5212529146119624326">"현재 기기 상태에 대한 정보를 수집하여 이메일 메시지로 전송합니다. 버그 신고를 시작하여 전송할 준비가 되려면 약간 시간이 걸립니다."</string>
<string name="bugreport_option_interactive_title" msgid="7968287837902871289">"대화형 보고서"</string>
<string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"대부분의 경우 이 옵션을 사용합니다. 신고 진행 상황을 추적하고 문제에 대한 세부정보를 입력하고 스크린샷을 찍을 수 있습니다. 신고하기에 시간이 너무 오래 걸리고 사용 빈도가 낮은 일부 섹션을 생략할 수 있습니다."</string>
- <string name="bugreport_option_full_title" msgid="7681035745950045690">"전체 보고서"</string>
+ <string name="bugreport_option_full_title" msgid="7681035745950045690">"전체 신고"</string>
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"기기가 응답하지 않거나 너무 느리거나 모든 보고서 섹션이 필요한 경우 이 옵션을 사용하여 시스템 방해를 최소화합니다. 세부정보를 추가하거나 스크린샷을 추가로 찍을 수 없습니다."</string>
<string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{버그 신고 스크린샷을 #초 후에 찍습니다.}other{버그 신고 스크린샷을 #초 후에 찍습니다.}}"</string>
<string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"버그 신고용 스크린샷 촬영 완료"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 688d6b244d12..13487f77b7a4 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1958,7 +1958,7 @@
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Šī lietotne pieprasa papildu drošību. Mēģiniet tai piekļūt savā tālrunī."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Šajā ierīcē (<xliff:g id="DEVICE">%1$s</xliff:g>) nevar piekļūt tālvadībai. Mēģiniet tai piekļūt savā Android TV ierīcē."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Šajā ierīcē (<xliff:g id="DEVICE">%1$s</xliff:g>) nevar piekļūt tālvadībai. Mēģiniet tai piekļūt savā planšetdatorā."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Šajā ierīcē (<xliff:g id="DEVICE">%1$s</xliff:g>) nevar piekļūt tālvadībai. Mēģiniet tai piekļūt savā tālrunī."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Ierīcē <xliff:g id="DEVICE">%1$s</xliff:g> nevar piekļūt šai funkcijai. Mēģiniet tai piekļūt tālrunī."</string>
<string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Šī lietotne tika izstrādāta vecākai Android versijai un var nedarboties pareizi. Meklējiet atjauninājumus vai sazinieties ar izstrādātāju."</string>
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Meklēt atjauninājumu"</string>
<string name="new_sms_notification_title" msgid="6528758221319927107">"Jums ir jaunas īsziņas."</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index abc586770796..eab1b3eb4b71 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -124,7 +124,7 @@
<string name="roamingTextSearching" msgid="5323235489657753486">"Leter etter tjeneste"</string>
<string name="wfcRegErrorTitle" msgid="3193072971584858020">"Kunne ikke konfigurere wifi-anrop"</string>
<string-array name="wfcOperatorErrorAlertMessages">
- <item msgid="468830943567116703">"For å ringe og sende meldinger over Wi-Fi, må du først be operatøren om å konfigurere denne tjenesten. Deretter slår du på wifi-anrop igjen fra Innstillinger. (Feilkode: <xliff:g id="CODE">%1$s</xliff:g>)"</item>
+ <item msgid="468830943567116703">"For å ringe og sende meldinger over Wifi, må du først be operatøren om å konfigurere denne tjenesten. Deretter slår du på wifi-anrop igjen fra Innstillinger. (Feilkode: <xliff:g id="CODE">%1$s</xliff:g>)"</item>
</string-array>
<string-array name="wfcOperatorErrorNotificationMessages">
<item msgid="4795145070505729156">"Problem med å registrere wifi-anrop med operatøren din: <xliff:g id="CODE">%1$s</xliff:g>"</item>
@@ -135,7 +135,7 @@
<string name="wfcSpnFormat_spn_wifi_calling_vo_hyphen" msgid="3836827895369365298">"<xliff:g id="SPN">%s</xliff:g>-Wifi-anrop"</string>
<string name="wfcSpnFormat_wlan_call" msgid="4895315549916165700">"WLAN-anrop"</string>
<string name="wfcSpnFormat_spn_wlan_call" msgid="255919245825481510">"<xliff:g id="SPN">%s</xliff:g> WLAN-anrop"</string>
- <string name="wfcSpnFormat_spn_wifi" msgid="7232899594327126970">"<xliff:g id="SPN">%s</xliff:g> Wi-Fi"</string>
+ <string name="wfcSpnFormat_spn_wifi" msgid="7232899594327126970">"<xliff:g id="SPN">%s</xliff:g> Wifi"</string>
<string name="wfcSpnFormat_wifi_calling_bar_spn" msgid="8383917598312067365">"Wifi-anrop | <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="wfcSpnFormat_spn_vowifi" msgid="6865214948822061486">"<xliff:g id="SPN">%s</xliff:g> VoWifi"</string>
<string name="wfcSpnFormat_wifi_calling" msgid="6178935388378661755">"Wifi-anrop"</string>
@@ -143,9 +143,9 @@
<string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"Wifi-anrop"</string>
<string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string>
<string name="wifi_calling_off_summary" msgid="5626710010766902560">"Av"</string>
- <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Ring via Wi-Fi"</string>
+ <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Ring via Wifi"</string>
<string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"Ring over mobilnettverk"</string>
- <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Bare Wi-Fi"</string>
+ <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Bare Wifi"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
<string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>-reserve for anrop"</string>
@@ -516,14 +516,14 @@
<string name="permdesc_changeNetworkState" msgid="649341947816898736">"Lar appen endre innstillingene for nettverkstilknytning."</string>
<string name="permlab_changeTetherState" msgid="9079611809931863861">"endre tilknytningsoppsett"</string>
<string name="permdesc_changeTetherState" msgid="3025129606422533085">"Lar appen endre innstillingene for delt nettforbindelse."</string>
- <string name="permlab_accessWifiState" msgid="5552488500317911052">"se Wi-Fi-tilkoblinger"</string>
- <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Lar appen se informasjon om Wi-Fi-nettverk, f.eks. hvorvidt Wi-Fi er aktivert og navn på tilkoblede Wi-Fi-enheter."</string>
- <string name="permlab_changeWifiState" msgid="7947824109713181554">"koble til og fra Wi-Fi"</string>
- <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Lar appen koble til og fra Wi-Fi-tilgangspunkter, og å gjøre endringer i enhetens konfigurasjon for Wi-Fi-nettverk."</string>
+ <string name="permlab_accessWifiState" msgid="5552488500317911052">"se Wifi-tilkoblinger"</string>
+ <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Lar appen se informasjon om Wifi-nettverk, f.eks. hvorvidt Wifi er aktivert og navn på tilkoblede Wifi-enheter."</string>
+ <string name="permlab_changeWifiState" msgid="7947824109713181554">"koble til og fra wifi"</string>
+ <string name="permdesc_changeWifiState" msgid="7170350070554505384">"Lar appen koble til og fra wifi-tilgangspunkter, og å gjøre endringer i enhetens konfigurasjon for wifi-nettverk."</string>
<string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"tillate multicast for trådløse nettverk"</string>
- <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Lar appen motta pakker som sendes til alle enhetene på et Wi-Fi-nettverk ved hjelp av multikastingsadresser, Dette bruker mer strøm enn modusen uten multikasting."</string>
- <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Lar appen motta pakker som sendes til alle enhetene på et Wi-Fi-nettverk ved hjelp av multikastingsadresser, ikke bare Android TV-enheten din. Dette bruker mer strøm enn modus uten multikasting."</string>
- <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Lar appen motta pakker som sendes til alle enhetene på et Wi-Fi-nettverk ved hjelp av multikastingsadresser, Dette bruker mer strøm enn modusen uten multikasting."</string>
+ <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Lar appen motta pakker som sendes til alle enhetene på et Wifi-nettverk ved hjelp av multikastingsadresser, Dette bruker mer strøm enn modusen uten multikasting."</string>
+ <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Lar appen motta pakker som sendes til alle enhetene på et Wifi-nettverk ved hjelp av multikastingsadresser, ikke bare Android TV-enheten din. Dette bruker mer strøm enn modus uten multikasting."</string>
+ <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Lar appen motta pakker som sendes til alle enhetene på et Wifi-nettverk ved hjelp av multikastingsadresser, Dette bruker mer strøm enn modusen uten multikasting."</string>
<string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"endre Bluetooth-innstillinger"</string>
<string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Lar appen konfigurere det lokale Bluetooth-nettbrettet, samt oppdage og koble sammen med eksterne enheter."</string>
<string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Lar appen konfigurere Bluetooth på Android TV-enheten din samt oppdage og koble sammen med eksterne enheter."</string>
@@ -546,8 +546,8 @@
<string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Lar appen vise annonser til Bluetooth-enheter i nærheten"</string>
<string name="permlab_uwb_ranging" msgid="8141915781475770665">"fastslå relativ posisjon mellom enheter som bruker ultrabredbånd"</string>
<string name="permdesc_uwb_ranging" msgid="2519723069604307055">"tillate at appen fastslår den relative posisjonen mellom enheter i nærheten som bruker ultrabredbånd"</string>
- <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"samhandle med Wi-Fi-enheter i nærheten"</string>
- <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Lar appen annonsere, koble til og fastslå den relative posisjonen til Wi-Fi-enheter i nærheten"</string>
+ <string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"samhandle med wifi-enheter i nærheten"</string>
+ <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Lar appen annonsere, koble til og fastslå den relative posisjonen til wifi-enheter i nærheten"</string>
<string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Informasjon om prioritert NFC-betalingstjeneste"</string>
<string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Gir appen tilgang til informasjon om prioritert NFC-betalingstjeneste, for eksempel registrerte hjelpemidler og destinasjon."</string>
<string name="permlab_nfc" msgid="1904455246837674977">"kontroller overføring av data med NFC-teknologi"</string>
@@ -1293,7 +1293,7 @@
<string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Alarmlyder"</string>
<string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Varsellyder"</string>
<string name="ringtone_unknown" msgid="5059495249862816475">"Ukjent"</string>
- <string name="wifi_available_sign_in" msgid="381054692557675237">"Logg på Wi-Fi-nettverket"</string>
+ <string name="wifi_available_sign_in" msgid="381054692557675237">"Logg på Wifi-nettverket"</string>
<string name="network_available_sign_in" msgid="1520342291829283114">"Logg på nettverk"</string>
<!-- no translation found for network_available_sign_in_detailed (7520423801613396556) -->
<skip />
@@ -1576,10 +1576,10 @@
<string name="data_usage_warning_title" msgid="9034893717078325845">"Varsel om databruk"</string>
<string name="data_usage_warning_body" msgid="1669325367188029454">"Du har brukt <xliff:g id="APP">%s</xliff:g> med data"</string>
<string name="data_usage_mobile_limit_title" msgid="3911447354393775241">"Grensen for mobildata er nådd"</string>
- <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Datagrensen for Wi-Fi er nådd"</string>
+ <string name="data_usage_wifi_limit_title" msgid="2069698056520812232">"Datagrensen for wifi er nådd"</string>
<string name="data_usage_limit_body" msgid="3567699582000085710">"Data er på pause i resten av syklusen"</string>
<string name="data_usage_mobile_limit_snoozed_title" msgid="101888478915677895">"Over grensen for mobildata"</string>
- <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"Over grensen din for Wi-Fi-data"</string>
+ <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"Over grensen din for wifi-data"</string>
<string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"Du er <xliff:g id="SIZE">%s</xliff:g> over den angitte grensen din"</string>
<string name="data_usage_restricted_title" msgid="126711424380051268">"Bakgrunnsdata er begrenset"</string>
<string name="data_usage_restricted_body" msgid="5338694433686077733">"Trykk for å fjerne begrensningen."</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index c1c0d5440304..73a59789130e 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -296,7 +296,7 @@
<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 de segurança"</string>
<string name="android_system_label" msgid="5974767339591067210">"Sistema Android"</string>
- <string name="user_owner_label" msgid="8628726904184471211">"Deslize até o perfil pessoal"</string>
+ <string name="user_owner_label" msgid="8628726904184471211">"Mudar para o perfil pessoal"</string>
<string name="managed_profile_label" msgid="7316778766973512382">"Perfil de trabalho"</string>
<string name="permgrouplab_contacts" msgid="4254143639307316920">"Contatos"</string>
<string name="permgroupdesc_contacts" msgid="9163927941244182567">"acesse seus contatos"</string>
@@ -749,7 +749,7 @@
<string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Permite que o proprietário use serviços da operadora. Não deve ser necessário para apps comuns."</string>
<string name="permlab_access_notification_policy" msgid="5524112842876975537">"acessar \"Não perturbe\""</string>
<string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permitir que o app leia e grave a configuração \"Não perturbe\"."</string>
- <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"iniciar uso da permissão para visualização"</string>
+ <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"começar a usar a permissão para ver"</string>
<string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite que o sistema inicie o uso de permissão para um app. Não deve ser necessário para apps comuns."</string>
<string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"decisões de permissão da visualização inicial"</string>
<string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Autoriza o detentor a iniciar a tela para revisar as decisões de permissão. Não deve ser necessário para apps normais."</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 1fe6e1c2c3af..0efbc734c254 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -164,7 +164,7 @@
<string name="httpErrorAuth" msgid="469553140922938968">"Não foi possível autenticar."</string>
<string name="httpErrorProxyAuth" msgid="7229662162030113406">"A autenticação através do servidor proxy falhou."</string>
<string name="httpErrorConnect" msgid="3295081579893205617">"Não foi possível ligar ao servidor."</string>
- <string name="httpErrorIO" msgid="3860318696166314490">"Não foi possível comunicar com o servidor. Tente novamente mais tarde."</string>
+ <string name="httpErrorIO" msgid="3860318696166314490">"Não foi possível comunicar com o servidor. Tente mais tarde."</string>
<string name="httpErrorTimeout" msgid="7446272815190334204">"Esgotou o tempo limite da ligação ao servidor."</string>
<string name="httpErrorRedirectLoop" msgid="8455757777509512098">"A página contém demasiados redireccionamentos do servidor."</string>
<string name="httpErrorUnsupportedScheme" msgid="2664108769858966374">"O protocolo não é suportado."</string>
@@ -172,7 +172,7 @@
<string name="httpErrorBadUrl" msgid="754447723314832538">"Não foi possível abrir a página porque o URL é inválido."</string>
<string name="httpErrorFile" msgid="3400658466057744084">"Não foi possível aceder ao ficheiro."</string>
<string name="httpErrorFileNotFound" msgid="5191433324871147386">"Não foi possível localizar o ficheiro solicitado."</string>
- <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"Existem demasiados pedidos em processamento. Tente novamente mais tarde."</string>
+ <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"Existem demasiados pedidos em processamento. Tente mais tarde."</string>
<string name="notification_title" msgid="5783748077084481121">"Erro de início de sessão de <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
<string name="contentServiceSync" msgid="2341041749565687871">"Sincronização"</string>
<string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"Não é possível sincronizar"</string>
@@ -671,7 +671,7 @@
<string name="face_error_no_space" msgid="5649264057026021723">"Não pode guardar novos dados de rostos. Elimine um antigo."</string>
<string name="face_error_canceled" msgid="2164434737103802131">"Operação de rosto cancelada."</string>
<string name="face_error_user_canceled" msgid="5766472033202928373">"Desbloqueio facial cancelado pelo utilizador"</string>
- <string name="face_error_lockout" msgid="7864408714994529437">"Demasiadas tentativas. Tente novamente mais tarde."</string>
+ <string name="face_error_lockout" msgid="7864408714994529437">"Demasiadas tentativas. Tente mais tarde."</string>
<string name="face_error_lockout_permanent" msgid="3277134834042995260">"Demasiadas tentativas. O Desbloqueio facial foi desativado."</string>
<string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Demasiadas tentativas. Em alternativa, introduza o bloqueio de ecrã."</string>
<string name="face_error_unable_to_process" msgid="5723292697366130070">"Não é possível validar o rosto. Tente novamente."</string>
@@ -1836,7 +1836,7 @@
<string name="restr_pin_create_pin" msgid="917067613896366033">"Crie um PIN para modificar as restrições"</string>
<string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"Os PINs não correspondem. Tente novamente."</string>
<string name="restr_pin_error_too_short" msgid="1547007808237941065">"O PIN é demasiado pequeno. Deve ter, no mínimo, 4 dígitos."</string>
- <string name="restr_pin_try_later" msgid="5897719962541636727">"Tente novamente mais tarde"</string>
+ <string name="restr_pin_try_later" msgid="5897719962541636727">"Tente mais tarde"</string>
<string name="immersive_cling_title" msgid="2307034298721541791">"Visualização de ecrã inteiro"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Para sair, deslize rapidamente para baixo a partir da parte superior."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index c1c0d5440304..73a59789130e 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -296,7 +296,7 @@
<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 de segurança"</string>
<string name="android_system_label" msgid="5974767339591067210">"Sistema Android"</string>
- <string name="user_owner_label" msgid="8628726904184471211">"Deslize até o perfil pessoal"</string>
+ <string name="user_owner_label" msgid="8628726904184471211">"Mudar para o perfil pessoal"</string>
<string name="managed_profile_label" msgid="7316778766973512382">"Perfil de trabalho"</string>
<string name="permgrouplab_contacts" msgid="4254143639307316920">"Contatos"</string>
<string name="permgroupdesc_contacts" msgid="9163927941244182567">"acesse seus contatos"</string>
@@ -749,7 +749,7 @@
<string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Permite que o proprietário use serviços da operadora. Não deve ser necessário para apps comuns."</string>
<string name="permlab_access_notification_policy" msgid="5524112842876975537">"acessar \"Não perturbe\""</string>
<string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permitir que o app leia e grave a configuração \"Não perturbe\"."</string>
- <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"iniciar uso da permissão para visualização"</string>
+ <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"começar a usar a permissão para ver"</string>
<string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite que o sistema inicie o uso de permissão para um app. Não deve ser necessário para apps comuns."</string>
<string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"decisões de permissão da visualização inicial"</string>
<string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Autoriza o detentor a iniciar a tela para revisar as decisões de permissão. Não deve ser necessário para apps normais."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 86115d5a34ff..c2fe6ddd103a 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1953,13 +1953,13 @@
<string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Настройки телефона недоступны"</string>
<string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Эта функция пока недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте Android TV."</string>
<string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Эта функция пока недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте планшет."</string>
- <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Эта функция пока недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте телефон."</string>
+ <string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"На устройстве <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_for_settings_dialog" product="tv" msgid="820334666354451145">"Эта функция недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте Android TV."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Эта функция недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте планшет."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Эта функция недоступна на устройстве <xliff:g id="DEVICE">%1$s</xliff:g>. Используйте телефон."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"На устройстве <xliff:g id="DEVICE">%1$s</xliff:g> эта функция недоступна. Используйте телефон."</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>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 131ccd04426d..46a5ca4bb1de 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -499,7 +499,7 @@
<string name="permlab_setWallpaper" msgid="6959514622698794511">"వాల్‌పేపర్‌ను సెట్ చేయడం"</string>
<string name="permdesc_setWallpaper" msgid="2973996714129021397">"సిస్టమ్ వాల్‌పేపర్‌ను సెట్ చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
<string name="permlab_setWallpaperHints" msgid="1153485176642032714">"మీ వాల్‌పేపర్ పరిమాణాన్ని సర్దుబాటు చేయడం"</string>
- <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"సిస్టమ్ వాల్‌పేపర్ పరిమాణం సూచనలను సెట్ చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
+ <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"సిస్టమ్ వాల్‌పేపర్ సైజ్‌ సూచనలను సెట్ చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
<string name="permlab_setTimeZone" msgid="7922618798611542432">"సమయ మండలిని సెట్ చేయడం"</string>
<string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"టాబ్లెట్ యొక్క సమయ మండలిని మార్చడానికి యాప్‌ను అనుమతిస్తుంది."</string>
<string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"మీ Android TV పరికరం సమయ మండలిని మార్చడానికి యాప్‌ని అనుమతిస్తుంది."</string>
@@ -560,7 +560,7 @@
<string name="permdesc_postNotification" msgid="5974977162462877075">"నోటిఫికేషన్‌లను చూపించడానికి యాప్‌ను అనుమతించండి"</string>
<string name="permlab_useBiometric" msgid="6314741124749633786">"బయోమెట్రిక్ హార్డ్‌వేర్‌ని ఉపయోగించు"</string>
<string name="permdesc_useBiometric" msgid="7502858732677143410">"ప్రమాణీకరణ కోసం బయోమెట్రిక్ హార్డ్‌వేర్‌ను ఉపయోగించడానికి యాప్‌ని అనుమతిస్తుంది"</string>
- <string name="permlab_manageFingerprint" msgid="7432667156322821178">"వేలిముద్ర హార్డ్‌వేర్‌ని నిర్వహించడానికి అనుమతి"</string>
+ <string name="permlab_manageFingerprint" msgid="7432667156322821178">"వేలిముద్ర హార్డ్‌వేర్‌ని మేనేజ్ చేయడానికి అనుమతి"</string>
<string name="permdesc_manageFingerprint" msgid="2025616816437339865">"వినియోగం కోసం వేలిముద్ర టెంప్లేట్‌లను జోడించే, తొలగించే పద్ధతులను అమలు చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
<string name="permlab_useFingerprint" msgid="1001421069766751922">"వేలిముద్ర హార్డ్‌వేర్‌ని ఉపయోగించడానికి అనుమతి"</string>
<string name="permdesc_useFingerprint" msgid="412463055059323742">"ప్రామాణీకరణ కోసం వేలిముద్ర హార్డ్‌వేర్‌ను ఉపయోగించడానికి యాప్‌ను అనుమతిస్తుంది"</string>
@@ -709,7 +709,7 @@
<string name="permlab_register_call_provider" msgid="6135073566140050702">"కొత్త టెలికామ్ కనెక్షన్‌లను నమోదు చేయడం"</string>
<string name="permdesc_register_call_provider" msgid="4201429251459068613">"కొత్త టెలికామ్ కనెక్షన్‌లను నమోదు చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
<string name="permlab_connection_manager" msgid="3179365584691166915">"టెలికామ్ కనెక్షన్‌లను నిర్వహించడం"</string>
- <string name="permdesc_connection_manager" msgid="1426093604238937733">"టెలికామ్ కనెక్షన్‌లను నిర్వహించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
+ <string name="permdesc_connection_manager" msgid="1426093604238937733">"టెలికామ్ కనెక్షన్‌లను మేనేజ్ చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
<string name="permlab_bind_incall_service" msgid="5990625112603493016">"ఇన్-కాల్ స్క్రీన్‌తో పరస్పర చర్య చేయడం"</string>
<string name="permdesc_bind_incall_service" msgid="4124917526967765162">"వినియోగదారునికి ఇన్-కాల్ స్క్రీన్ ఎప్పుడు, ఎలా కనిపించాలనే దాన్ని నియంత్రించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
<string name="permlab_bind_connection_service" msgid="5409268245525024736">"టెలిఫోన్ సేవలతో పరస్పర చర్య చేయడం"</string>
@@ -719,7 +719,7 @@
<string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"చారిత్రక నెట్‌వర్క్ వినియోగాన్ని చదవడం"</string>
<string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"నిర్దిష్ట నెట్‌వర్క్‌లు మరియు యాప్‌ల కోసం చారిత్రాత్మక నెట్‌వర్క్ వినియోగాన్ని చదవడానికి యాప్‌ను అనుమతిస్తుంది."</string>
<string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"నెట్‌వర్క్ విధానాన్ని నిర్వహించడం"</string>
- <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"నెట్‌వర్క్ విధానాలను నిర్వహించడానికి మరియు యాప్-నిర్దిష్ట నిబంధనలను నిర్వచించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
+ <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"నెట్‌వర్క్ విధానాలను మేనేజ్ చేయడానికి మరియు యాప్-నిర్దిష్ట నిబంధనలను నిర్వచించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
<string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"నెట్‌వర్క్ వినియోగ అకౌంటింగ్‌ను ఎడిట్ చేయడం"</string>
<string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"యాప్‌లలో నెట్‌వర్క్ వినియోగం ఎలా గణించాలనే దాన్ని ఎడిట్ చేయడానికి యాప్‌ను అనుమతిస్తుంది. సాధారణ యాప్‌ల ద్వారా ఉపయోగించడానికి ఉద్దేశించినది కాదు."</string>
<string name="permlab_accessNotifications" msgid="7130360248191984741">"నోటిఫికేషన్‌లను యాక్సెస్ చేయడం"</string>
@@ -1446,7 +1446,7 @@
<string name="ext_media_status_ejecting" msgid="7532403368044013797">"తొలగిస్తోంది…"</string>
<string name="ext_media_status_formatting" msgid="774148701503179906">"ఫార్మాట్ చేస్తోంది..."</string>
<string name="ext_media_status_missing" msgid="6520746443048867314">"చొప్పించబడలేదు"</string>
- <string name="activity_list_empty" msgid="4219430010716034252">"సరిపోలే కార్యాచరణలు కనుగొనబడలేదు."</string>
+ <string name="activity_list_empty" msgid="4219430010716034252">"మ్యాచ్ అయ్యే కార్యాచరణలు కనుగొనబడలేదు."</string>
<string name="permlab_route_media_output" msgid="8048124531439513118">"మీడియా అవుట్‌పుట్‌ను మళ్లించడం"</string>
<string name="permdesc_route_media_output" msgid="1759683269387729675">"మీడియా అవుట్‌పుట్‌ను ఇతర బాహ్య పరికరాలకు మళ్లించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
<string name="permlab_readInstallSessions" msgid="7279049337895583621">"ఇన్‌స్టాల్ సెషన్‌లను చదవడం"</string>
@@ -1491,8 +1491,8 @@
<string name="notification_ranker_binding_label" msgid="432708245635563763">"నోటిఫికేషన్ ర్యాంకర్ సేవ"</string>
<string name="vpn_title" msgid="5906991595291514182">"VPN సక్రియం చేయబడింది"</string>
<string name="vpn_title_long" msgid="6834144390504619998">"<xliff:g id="APP">%s</xliff:g> ద్వారా VPN సక్రియం చేయబడింది"</string>
- <string name="vpn_text" msgid="2275388920267251078">"నెట్‌వర్క్‌ను నిర్వహించడానికి నొక్కండి."</string>
- <string name="vpn_text_long" msgid="278540576806169831">"<xliff:g id="SESSION">%s</xliff:g>కు కనెక్ట్ చేయబడింది. నెట్‌వర్క్‌ను నిర్వహించడానికి నొక్కండి."</string>
+ <string name="vpn_text" msgid="2275388920267251078">"నెట్‌వర్క్‌ను మేనేజ్ చేయడానికి నొక్కండి."</string>
+ <string name="vpn_text_long" msgid="278540576806169831">"<xliff:g id="SESSION">%s</xliff:g>కు కనెక్ట్ చేయబడింది. నెట్‌వర్క్‌ను మేనేజ్ చేయడానికి నొక్కండి."</string>
<string name="vpn_lockdown_connecting" msgid="6096725311950342607">"ఎల్లప్పుడూ-ఆన్‌లో ఉండే VPN కనెక్ట్ చేయబడుతోంది…"</string>
<string name="vpn_lockdown_connected" msgid="2853127976590658469">"ఎల్లప్పుడూ-ఆన్‌లో ఉండే VPN కనెక్ట్ చేయబడింది"</string>
<string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"ఎల్లప్పుడూ ఆన్‌లో ఉండే VPN నుండి డిస్‌కనెక్ట్ చేయబడింది"</string>
@@ -1723,7 +1723,7 @@
<string name="guest_name" msgid="8502103277839834324">"గెస్ట్"</string>
<string name="error_message_title" msgid="4082495589294631966">"ఎర్రర్"</string>
<string name="error_message_change_not_allowed" msgid="843159705042381454">"ఈ మార్పును మీ నిర్వాహకులు అనుమతించలేదు"</string>
- <string name="app_not_found" msgid="3429506115332341800">"ఈ చర్యను నిర్వహించడానికి యాప్ ఏదీ కనుగొనబడలేదు"</string>
+ <string name="app_not_found" msgid="3429506115332341800">"ఈ చర్యను మేనేజ్ చేయడానికి యాప్ ఏదీ కనుగొనబడలేదు"</string>
<string name="revoke" msgid="5526857743819590458">"ఉపసంహరించండి"</string>
<string name="mediasize_iso_a0" msgid="7039061159929977973">"ISO A0"</string>
<string name="mediasize_iso_a1" msgid="4063589931031977223">"ISO A1"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 7bb0ba9c91da..ea26a150d1fb 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -988,7 +988,7 @@
<string name="keyguard_accessibility_unlock_area_collapsed" msgid="4729922043778400434">"Pinaliit ang bahagi ng pag-unlock."</string>
<string name="keyguard_accessibility_widget" msgid="6776892679715699875">"<xliff:g id="WIDGET_INDEX">%1$s</xliff:g> widget."</string>
<string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"Tagapili ng user"</string>
- <string name="keyguard_accessibility_status" msgid="6792745049712397237">"Katayuan"</string>
+ <string name="keyguard_accessibility_status" msgid="6792745049712397237">"Status"</string>
<string name="keyguard_accessibility_camera" msgid="7862557559464986528">"Camera"</string>
<string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Mga kontrol ng media"</string>
<string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"Nagsimula na ang pagbabago ng ayos ng widget."</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index d46bdc8c3f2f..d0d585a006d8 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1562,7 +1562,7 @@
<string name="content_description_sliding_handle" msgid="982510275422590757">"滑动手柄。触摸并按住。"</string>
<string name="description_target_unlock_tablet" msgid="7431571180065859551">"滑动解锁。"</string>
<string name="action_bar_home_description" msgid="1501655419158631974">"导航首页"</string>
- <string name="action_bar_up_description" msgid="6611579697195026932">"向上导航"</string>
+ <string name="action_bar_up_description" msgid="6611579697195026932">"返回"</string>
<string name="action_menu_overflow_description" msgid="4579536843510088170">"更多选项"</string>
<string name="action_bar_home_description_format" msgid="5087107531331621803">"%1$s:%2$s"</string>
<string name="action_bar_home_subtitle_description_format" msgid="4346835454749569826">"%1$s - %2$s:%3$s"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 43de5ba95aa0..8f720503a06c 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1950,13 +1950,13 @@
<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="4003011766528814377">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用 Android TV 裝置。"</string>
- <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用平板電腦。"</string>
+ <string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個項目,請改用平板電腦。"</string>
<string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"目前無法在 <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_for_settings_dialog" product="tv" msgid="820334666354451145">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用 Android TV 裝置。"</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用平板電腦。"</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個項目,請改用平板電腦。"</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用手機。"</string>
<string name="deprecated_target_sdk_message" msgid="5203207875657579953">"這個應用程式是專為舊版 Android 所打造,因此可能無法正常運作。請嘗試檢查更新,或是與開發人員聯絡。"</string>
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"檢查更新"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 84280605862f..881d49991720 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -701,7 +701,7 @@
<!-- Indicates the time needed to time out the fold animation if the device stops in half folded
mode. -->
- <integer name="config_unfoldTransitionHalfFoldedTimeout">600</integer>
+ <integer name="config_unfoldTransitionHalfFoldedTimeout">1000</integer>
<!-- Indicates that the device supports having more than one internal display on at the same
time. Only applicable to devices with more than one internal display. If this option is
@@ -2479,6 +2479,13 @@
<integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes. -->
<!-- Is the system user the only user allowed to dream. -->
<bool name="config_dreamsOnlyEnabledForSystemUser">false</bool>
+ <!-- Whether dreams are disabled when ambient mode is suppressed. -->
+ <bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool>
+
+ <!-- The duration in milliseconds of the dream opening animation. -->
+ <integer name="config_dreamOpenAnimationDuration">250</integer>
+ <!-- The duration in milliseconds of the dream closing animation. -->
+ <integer name="config_dreamCloseAnimationDuration">100</integer>
<!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to
assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
@@ -3575,9 +3582,9 @@
config_sidefpsSkipWaitForPowerVendorAcquireMessage -->
<integer name="config_sidefpsSkipWaitForPowerAcquireMessage">6</integer>
- <!-- This vendor acquired message that will cause the sidefpsKgPowerPress window to be skipped.
- config_sidefpsSkipWaitForPowerOnFingerUp must be true and
- config_sidefpsSkipWaitForPowerAcquireMessage must be BIOMETRIC_ACQUIRED_VENDOR == 6. -->
+ <!-- This vendor acquired message will cause the sidefpsKgPowerPress window to be skipped
+ when config_sidefpsSkipWaitForPowerAcquireMessage == 6 (VENDOR) and the vendor acquire
+ message equals this constant -->
<integer name="config_sidefpsSkipWaitForPowerVendorAcquireMessage">2</integer>
<!-- This config is used to force VoiceInteractionService to start on certain low ram devices.
@@ -5956,4 +5963,8 @@
TODO(b/236022708) Move rear display state to device state config file
-->
<integer name="config_deviceStateRearDisplay">-1</integer>
+
+ <!-- Whether the lock screen is allowed to run its own live wallpaper,
+ different from the home screen wallpaper. -->
+ <bool name="config_independentLockscreenLiveWallpaper">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 97105d80bf79..7a7b43a3632a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2235,7 +2235,10 @@
<java-symbol type="integer" name="config_dreamsBatteryLevelMinimumWhenNotPowered" />
<java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" />
<java-symbol type="string" name="config_dreamsDefaultComponent" />
+ <java-symbol type="bool" name="config_dreamsDisabledByAmbientModeSuppressionConfig" />
<java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" />
+ <java-symbol type="integer" name="config_dreamOpenAnimationDuration" />
+ <java-symbol type="integer" name="config_dreamCloseAnimationDuration" />
<java-symbol type="array" name="config_supportedDreamComplications" />
<java-symbol type="array" name="config_disabledDreamComponents" />
<java-symbol type="bool" name="config_dismissDreamOnActivityStart" />
@@ -4846,6 +4849,7 @@
<java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
<java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
<java-symbol type="integer" name="config_deviceStateRearDisplay"/>
+ <java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
<!-- For app language picker -->
<java-symbol type="string" name="system_locale_title" />
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 0b8b29b9dda9..bcb13d2108b8 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -48,6 +48,7 @@ import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsWi
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertEquals;
@@ -56,7 +57,9 @@ import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.annotation.Nullable;
import android.app.Notification.CallStyle;
@@ -68,6 +71,7 @@ import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
+import android.graphics.Typeface;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
@@ -79,7 +83,9 @@ import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
import android.text.style.TextAppearanceSpan;
+import android.util.Pair;
import android.widget.RemoteViews;
import androidx.test.InstrumentationRegistry;
@@ -89,6 +95,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.util.ContrastColorUtil;
+import junit.framework.Assert;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -218,8 +226,10 @@ public class NotificationTest {
@Test
public void allPendingIntents_recollectedAfterReusingBuilder() {
- PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
- PendingIntent intent2 = PendingIntent.getActivity(mContext, 0, new Intent("test2"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent intent1 = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+ PendingIntent intent2 = PendingIntent.getActivity(
+ mContext, 0, new Intent("test2"), PendingIntent.FLAG_IMMUTABLE);
Notification.Builder builder = new Notification.Builder(mContext, "channel");
builder.setContentIntent(intent1);
@@ -669,30 +679,23 @@ public class NotificationTest {
Notification notification = new Notification.Builder(mContext, "Channel").setStyle(
style).build();
+ int targetSize = mContext.getResources().getDimensionPixelSize(
+ ActivityManager.isLowRamDeviceStatic()
+ ? R.dimen.notification_person_icon_max_size_low_ram
+ : R.dimen.notification_person_icon_max_size);
+
Bitmap personIcon = style.getUser().getIcon().getBitmap();
- assertThat(personIcon.getWidth()).isEqualTo(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_person_icon_max_size));
- assertThat(personIcon.getHeight()).isEqualTo(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_person_icon_max_size));
+ assertThat(personIcon.getWidth()).isEqualTo(targetSize);
+ assertThat(personIcon.getHeight()).isEqualTo(targetSize);
Bitmap avatarIcon = style.getMessages().get(0).getSenderPerson().getIcon().getBitmap();
- assertThat(avatarIcon.getWidth()).isEqualTo(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_person_icon_max_size));
- assertThat(avatarIcon.getHeight()).isEqualTo(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_person_icon_max_size));
+ assertThat(avatarIcon.getWidth()).isEqualTo(targetSize);
+ assertThat(avatarIcon.getHeight()).isEqualTo(targetSize);
Bitmap historicAvatarIcon = style.getHistoricMessages().get(
0).getSenderPerson().getIcon().getBitmap();
- assertThat(historicAvatarIcon.getWidth()).isEqualTo(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_person_icon_max_size));
- assertThat(historicAvatarIcon.getHeight()).isEqualTo(
- mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_person_icon_max_size));
+ assertThat(historicAvatarIcon.getWidth()).isEqualTo(targetSize);
+ assertThat(historicAvatarIcon.getHeight()).isEqualTo(targetSize);
}
@Test
@@ -780,7 +783,6 @@ public class NotificationTest {
assertFalse(notification.isMediaNotification());
}
- @Test
public void validateColorizedPaletteForColor(int rawColor) {
Notification.Colors cDay = new Notification.Colors();
Notification.Colors cNight = new Notification.Colors();
@@ -861,19 +863,22 @@ public class NotificationTest {
Bundle fakeTypes = new Bundle();
fakeTypes.putParcelable(EXTRA_LARGE_ICON_BIG, new Bundle());
- style.restoreFromExtras(fakeTypes);
// no crash, good
}
@Test
public void testRestoreFromExtras_Messaging_invalidExtra_noCrash() {
- Notification.Style style = new Notification.MessagingStyle();
+ Notification.Style style = new Notification.MessagingStyle("test");
Bundle fakeTypes = new Bundle();
fakeTypes.putParcelable(EXTRA_MESSAGING_PERSON, new Bundle());
fakeTypes.putParcelable(EXTRA_CONVERSATION_ICON, new Bundle());
- style.restoreFromExtras(fakeTypes);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setStyle(style)
+ .setExtras(fakeTypes)
+ .build();
+ Notification.Builder.recoverBuilder(mContext, n);
// no crash, good
}
@@ -885,22 +890,33 @@ public class NotificationTest {
fakeTypes.putParcelable(EXTRA_MEDIA_SESSION, new Bundle());
fakeTypes.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, new Bundle());
- style.restoreFromExtras(fakeTypes);
+ Notification n = new Notification.Builder(mContext, "test")
+ .setStyle(style)
+ .setExtras(fakeTypes)
+ .build();
+ Notification.Builder.recoverBuilder(mContext, n);
// no crash, good
}
@Test
public void testRestoreFromExtras_Call_invalidExtra_noCrash() {
- Notification.Style style = new CallStyle();
+ PendingIntent intent1 = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+ Notification.Style style = Notification.CallStyle.forIncomingCall(
+ new Person.Builder().setName("hi").build(), intent1, intent1);
+
Bundle fakeTypes = new Bundle();
fakeTypes.putParcelable(EXTRA_CALL_PERSON, new Bundle());
fakeTypes.putParcelable(EXTRA_ANSWER_INTENT, new Bundle());
fakeTypes.putParcelable(EXTRA_DECLINE_INTENT, new Bundle());
fakeTypes.putParcelable(EXTRA_HANG_UP_INTENT, new Bundle());
- style.restoreFromExtras(fakeTypes);
-
+ Notification n = new Notification.Builder(mContext, "test")
+ .setStyle(style)
+ .setExtras(fakeTypes)
+ .build();
+ Notification.Builder.recoverBuilder(mContext, n);
// no crash, good
}
@@ -962,7 +978,11 @@ public class NotificationTest {
fakeTypes.putParcelable(KEY_ON_READ, new Bundle());
fakeTypes.putParcelable(KEY_ON_REPLY, new Bundle());
fakeTypes.putParcelable(KEY_REMOTE_INPUT, new Bundle());
- Notification.CarExtender.UnreadConversation.getUnreadConversationFromBundle(fakeTypes);
+
+ Notification n = new Notification.Builder(mContext, "test")
+ .setExtras(fakeTypes)
+ .build();
+ Notification.CarExtender extender = new Notification.CarExtender(n);
// no crash, good
}
@@ -980,6 +1000,493 @@ public class NotificationTest {
// no crash, good
}
+
+ @Test
+ public void testDoesNotStripsExtenders() {
+ Notification.Builder nb = new Notification.Builder(mContext, "channel");
+ nb.extend(new Notification.CarExtender().setColor(Color.RED));
+ nb.extend(new Notification.TvExtender().setChannelId("different channel"));
+ nb.extend(new Notification.WearableExtender().setDismissalId("dismiss"));
+ Notification before = nb.build();
+ Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before);
+
+ assertTrue(before == after);
+
+ Assert.assertEquals("different channel",
+ new Notification.TvExtender(before).getChannelId());
+ Assert.assertEquals(Color.RED, new Notification.CarExtender(before).getColor());
+ Assert.assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId());
+ }
+
+ @Test
+ public void testStyleChangeVisiblyDifferent_noStyles() {
+ Notification.Builder n1 = new Notification.Builder(mContext, "test");
+ Notification.Builder n2 = new Notification.Builder(mContext, "test");
+
+ assertFalse(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testStyleChangeVisiblyDifferent_noStyleToStyle() {
+ Notification.Builder n1 = new Notification.Builder(mContext, "test");
+ Notification.Builder n2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigTextStyle());
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testStyleChangeVisiblyDifferent_styleToNoStyle() {
+ Notification.Builder n2 = new Notification.Builder(mContext, "test");
+ Notification.Builder n1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigTextStyle());
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testStyleChangeVisiblyDifferent_changeStyle() {
+ Notification.Builder n1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.InboxStyle());
+ Notification.Builder n2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigTextStyle());
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testInboxTextChange() {
+ Notification.Builder nInbox1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.InboxStyle().addLine("a").addLine("b"));
+ Notification.Builder nInbox2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.InboxStyle().addLine("b").addLine("c"));
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nInbox1, nInbox2));
+ }
+
+ @Test
+ public void testBigTextTextChange() {
+ Notification.Builder nBigText1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigTextStyle().bigText("something"));
+ Notification.Builder nBigText2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigTextStyle().bigText("else"));
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigText1, nBigText2));
+ }
+
+ @Test
+ public void testBigPictureChange() {
+ Bitmap bitA = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+ Bitmap bitB = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
+
+ Notification.Builder nBigPic1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigPictureStyle().bigPicture(bitA));
+ Notification.Builder nBigPic2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigPictureStyle().bigPicture(bitB));
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2));
+ }
+
+ @Test
+ public void testMessagingChange_text() {
+ Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message(
+ "a", 100, new Person.Builder().setName("hi").build())));
+ Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message(
+ "a", 100, new Person.Builder().setName("hi").build()))
+ .addMessage(new Notification.MessagingStyle.Message(
+ "b", 100, new Person.Builder().setName("hi").build()))
+ );
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+ }
+
+ @Test
+ public void testMessagingChange_data() {
+ Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message(
+ "a", 100, new Person.Builder().setName("hi").build())
+ .setData("text", mock(Uri.class))));
+ Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message(
+ "a", 100, new Person.Builder().setName("hi").build())));
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+ }
+
+ @Test
+ public void testMessagingChange_sender() {
+ Person a = new Person.Builder().setName("A").build();
+ Person b = new Person.Builder().setName("b").build();
+ Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
+ Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+ }
+
+ @Test
+ public void testMessagingChange_key() {
+ Person a = new Person.Builder().setName("hi").setKey("A").build();
+ Person b = new Person.Builder().setName("hi").setKey("b").build();
+ Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
+ Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
+
+ assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+ }
+
+ @Test
+ public void testMessagingChange_ignoreTimeChange() {
+ Notification.Builder nM1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message(
+ "a", 100, new Person.Builder().setName("hi").build())));
+ Notification.Builder nM2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle("")
+ .addMessage(new Notification.MessagingStyle.Message(
+ "a", 1000, new Person.Builder().setName("hi").build()))
+ );
+
+ assertFalse(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
+ }
+
+ @Test
+ public void testRemoteViews_nullChange() {
+ Notification.Builder n1 = new Notification.Builder(mContext, "test")
+ .setContent(mock(RemoteViews.class));
+ Notification.Builder n2 = new Notification.Builder(mContext, "test");
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test");
+ n2 = new Notification.Builder(mContext, "test")
+ .setContent(mock(RemoteViews.class));
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test")
+ .setCustomBigContentView(mock(RemoteViews.class));
+ n2 = new Notification.Builder(mContext, "test");
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test");
+ n2 = new Notification.Builder(mContext, "test")
+ .setCustomBigContentView(mock(RemoteViews.class));
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test");
+ n2 = new Notification.Builder(mContext, "test");
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+ }
+
+ @Test
+ public void testRemoteViews_layoutChange() {
+ RemoteViews a = mock(RemoteViews.class);
+ when(a.getLayoutId()).thenReturn(234);
+ RemoteViews b = mock(RemoteViews.class);
+ when(b.getLayoutId()).thenReturn(189);
+
+ Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+ Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+ }
+
+ @Test
+ public void testRemoteViews_layoutSame() {
+ RemoteViews a = mock(RemoteViews.class);
+ when(a.getLayoutId()).thenReturn(234);
+ RemoteViews b = mock(RemoteViews.class);
+ when(b.getLayoutId()).thenReturn(234);
+
+ Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+ Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+ }
+
+ @Test
+ public void testRemoteViews_sequenceChange() {
+ RemoteViews a = mock(RemoteViews.class);
+ when(a.getLayoutId()).thenReturn(234);
+ when(a.getSequenceNumber()).thenReturn(1);
+ RemoteViews b = mock(RemoteViews.class);
+ when(b.getLayoutId()).thenReturn(234);
+ when(b.getSequenceNumber()).thenReturn(2);
+
+ Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+ Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+ assertTrue(Notification.areRemoteViewsChanged(n1, n2));
+ }
+
+ @Test
+ public void testRemoteViews_sequenceSame() {
+ RemoteViews a = mock(RemoteViews.class);
+ when(a.getLayoutId()).thenReturn(234);
+ when(a.getSequenceNumber()).thenReturn(1);
+ RemoteViews b = mock(RemoteViews.class);
+ when(b.getLayoutId()).thenReturn(234);
+ when(b.getSequenceNumber()).thenReturn(1);
+
+ Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
+ Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+
+ n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
+ n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
+ assertFalse(Notification.areRemoteViewsChanged(n1, n2));
+ }
+
+ @Test
+ public void testActionsDifferent_null() {
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .build();
+
+ assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testActionsDifferentSame() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+ .build();
+
+ assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testActionsDifferentText() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
+ .build();
+
+ assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testActionsDifferentSpannables() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon,
+ new SpannableStringBuilder().append("test1",
+ new StyleSpan(Typeface.BOLD),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE),
+ intent).build())
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "test1", intent).build())
+ .build();
+
+ assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testActionsDifferentNumber() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
+ .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
+ .build();
+
+ assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testActionsDifferentIntent() {
+ PendingIntent intent1 = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+ PendingIntent intent2 = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent1).build())
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent2).build())
+ .build();
+
+ assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testActionsIgnoresRemoteInputs() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+ .addRemoteInput(new RemoteInput.Builder("a")
+ .setChoices(new CharSequence[] {"i", "m"})
+ .build())
+ .build())
+ .build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+ .addRemoteInput(new RemoteInput.Builder("a")
+ .setChoices(new CharSequence[] {"t", "m"})
+ .build())
+ .build())
+ .build();
+
+ assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
+ }
+
+ @Test
+ public void testFreeformRemoteInputActionPair_noRemoteInput() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+ Notification notification = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+ .build())
+ .build();
+ Assert.assertNull(notification.findRemoteInputActionPair(false));
+ }
+
+ @Test
+ public void testFreeformRemoteInputActionPair_hasRemoteInput() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ RemoteInput remoteInput = new RemoteInput.Builder("a").build();
+
+ Notification.Action actionWithRemoteInput =
+ new Notification.Action.Builder(icon, "TEXT 1", intent)
+ .addRemoteInput(remoteInput)
+ .addRemoteInput(remoteInput)
+ .build();
+
+ Notification.Action actionWithoutRemoteInput =
+ new Notification.Action.Builder(icon, "TEXT 2", intent)
+ .build();
+
+ Notification notification = new Notification.Builder(mContext, "test")
+ .addAction(actionWithoutRemoteInput)
+ .addAction(actionWithRemoteInput)
+ .build();
+
+ Pair<RemoteInput, Notification.Action> remoteInputActionPair =
+ notification.findRemoteInputActionPair(false);
+
+ assertNotNull(remoteInputActionPair);
+ Assert.assertEquals(remoteInput, remoteInputActionPair.first);
+ Assert.assertEquals(actionWithRemoteInput, remoteInputActionPair.second);
+ }
+
+ @Test
+ public void testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+ Notification notification = new Notification.Builder(mContext, "test")
+ .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
+ .addRemoteInput(
+ new RemoteInput.Builder("a")
+ .setAllowFreeFormInput(false).build())
+ .build())
+ .build();
+ Assert.assertNull(notification.findRemoteInputActionPair(true));
+ }
+
+ @Test
+ public void testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput() {
+ PendingIntent intent = PendingIntent.getActivity(
+ mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
+ Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ RemoteInput remoteInput =
+ new RemoteInput.Builder("a").setAllowFreeFormInput(false).build();
+ RemoteInput freeformRemoteInput =
+ new RemoteInput.Builder("b").setAllowFreeFormInput(true).build();
+
+ Notification.Action actionWithFreeformRemoteInput =
+ new Notification.Action.Builder(icon, "TEXT 1", intent)
+ .addRemoteInput(remoteInput)
+ .addRemoteInput(freeformRemoteInput)
+ .build();
+
+ Notification.Action actionWithoutFreeformRemoteInput =
+ new Notification.Action.Builder(icon, "TEXT 2", intent)
+ .addRemoteInput(remoteInput)
+ .build();
+
+ Notification notification = new Notification.Builder(mContext, "test")
+ .addAction(actionWithoutFreeformRemoteInput)
+ .addAction(actionWithFreeformRemoteInput)
+ .build();
+
+ Pair<RemoteInput, Notification.Action> remoteInputActionPair =
+ notification.findRemoteInputActionPair(true);
+
+ assertNotNull(remoteInputActionPair);
+ Assert.assertEquals(freeformRemoteInput, remoteInputActionPair.first);
+ Assert.assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second);
+ }
+
private void assertValid(Notification.Colors c) {
// Assert that all colors are populated
assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f448cb3091e7..f370ebd94545 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -60,6 +60,8 @@ public class WindowOnBackInvokedDispatcherTest {
private OnBackAnimationCallback mCallback1;
@Mock
private OnBackAnimationCallback mCallback2;
+ @Mock
+ private BackEvent mBackEvent;
@Before
public void setUp() throws Exception {
@@ -85,14 +87,14 @@ public class WindowOnBackInvokedDispatcherTest {
verify(mWindowSession, times(2)).setOnBackInvokedCallbackInfo(
Mockito.eq(mWindow),
captor.capture());
- captor.getAllValues().get(0).getCallback().onBackStarted();
+ captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted();
+ verify(mCallback1).onBackStarted(mBackEvent);
verifyZeroInteractions(mCallback2);
- captor.getAllValues().get(1).getCallback().onBackStarted();
+ captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted();
+ verify(mCallback2).onBackStarted(mBackEvent);
verifyNoMoreInteractions(mCallback1);
}
@@ -110,9 +112,9 @@ public class WindowOnBackInvokedDispatcherTest {
Mockito.eq(mWindow), captor.capture());
verifyNoMoreInteractions(mWindowSession);
assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
- captor.getValue().getCallback().onBackStarted();
+ captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted();
+ verify(mCallback1).onBackStarted(mBackEvent);
}
@Test
@@ -148,8 +150,8 @@ public class WindowOnBackInvokedDispatcherTest {
mDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_OVERLAY, mCallback2);
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
- captor.getValue().getCallback().onBackStarted();
+ captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted();
+ verify(mCallback2).onBackStarted(mBackEvent);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index d19f9f5ea58f..52feac5a585a 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -47,6 +47,7 @@ import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
+import android.util.Log;
import android.util.MutableInt;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
@@ -82,6 +83,7 @@ import java.util.function.IntConsumer;
* com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
*/
public class BatteryStatsNoteTest extends TestCase {
+ private static final String TAG = BatteryStatsNoteTest.class.getSimpleName();
private static final int UID = 10500;
private static final int ISOLATED_APP_ID = Process.FIRST_ISOLATED_UID + 23;
@@ -2031,6 +2033,115 @@ public class BatteryStatsNoteTest extends TestCase {
noRadioProcFlags, lastProcStateChangeFlags.value);
}
+
+
+ @SmallTest
+ public void testNoteMobileRadioPowerStateLocked() {
+ long curr;
+ boolean update;
+ final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+ final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setOnBatteryInternal(true);
+
+ // Note mobile radio is on.
+ curr = 1000L * (clocks.realtime = clocks.uptime = 1001);
+ bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+ UID);
+
+ // Note mobile radio is still on.
+ curr = 1000L * (clocks.realtime = clocks.uptime = 2001);
+ update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ curr, UID);
+ assertFalse(
+ "noteMobileRadioPowerStateLocked should not request an update when the power "
+ + "state does not change from HIGH.",
+ update);
+
+ // Note mobile radio is off.
+ curr = 1000L * (clocks.realtime = clocks.uptime = 3001);
+ update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ curr, UID);
+ assertTrue(
+ "noteMobileRadioPowerStateLocked should request an update when the power state "
+ + "changes from HIGH to LOW.",
+ update);
+
+ // Note mobile radio is still off.
+ curr = 1000L * (clocks.realtime = clocks.uptime = 4001);
+ update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ curr, UID);
+ assertFalse(
+ "noteMobileRadioPowerStateLocked should not request an update when the power "
+ + "state does not change from LOW.",
+ update);
+
+ // Note mobile radio is on.
+ curr = 1000L * (clocks.realtime = clocks.uptime = 5001);
+ update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ curr, UID);
+ assertFalse(
+ "noteMobileRadioPowerStateLocked should not request an update when the power "
+ + "state changes from LOW to HIGH.",
+ update);
+ }
+
+ @SmallTest
+ public void testNoteMobileRadioPowerStateLocked_rateLimited() {
+ long curr;
+ boolean update;
+ final MockClock clocks = new MockClock(); // holds realtime and uptime in ms
+ final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setPowerProfile(mock(PowerProfile.class));
+
+ final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels();
+ final ModemActivityInfo mai = new ModemActivityInfo(0L, 0L, 0L, new int[txLevelCount], 0L);
+
+ final long rateLimit = bi.getMobileRadioPowerStateUpdateRateLimit();
+ if (rateLimit < 0) {
+ Log.w(TAG, "Skipping testNoteMobileRadioPowerStateLocked_rateLimited, rateLimit = "
+ + rateLimit);
+ return;
+ }
+ bi.setOnBatteryInternal(true);
+
+ // Note mobile radio is on.
+ curr = 1000L * (clocks.realtime = clocks.uptime = 1001);
+ bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+ UID);
+
+ clocks.realtime = clocks.uptime = 2001;
+ mai.setTimestamp(clocks.realtime);
+ bi.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE,
+ clocks.realtime, clocks.uptime, mNetworkStatsManager);
+
+ // Note mobile radio is off within the rate limit duration.
+ clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7);
+ curr = 1000L * clocks.realtime;
+ update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ curr, UID);
+ assertFalse(
+ "noteMobileRadioPowerStateLocked should not request an update when the power "
+ + "state so soon after a noteModemControllerActivity",
+ update);
+
+ // Note mobile radio is on.
+ clocks.realtime = clocks.uptime = clocks.realtime + (long) (rateLimit * 0.7);
+ curr = 1000L * clocks.realtime;
+ bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, curr,
+ UID);
+
+ // Note mobile radio is off much later
+ clocks.realtime = clocks.uptime = clocks.realtime + rateLimit;
+ curr = 1000L * clocks.realtime;
+ update = bi.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ curr, UID);
+ assertTrue(
+ "noteMobileRadioPowerStateLocked should request an update when the power state "
+ + "changes from HIGH to LOW much later after a "
+ + "noteModemControllerActivity.",
+ update);
+ }
+
private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
// Note that noteUidProcessStateLocked uses ActivityManager process states.
if (fgOn) {
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index edeb5e9f4834..5ea4f069f026 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -114,6 +114,10 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
return getUidStatsLocked(uid).mOnBatteryScreenOffBackgroundTimeBase;
}
+ public long getMobileRadioPowerStateUpdateRateLimit() {
+ return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS;
+ }
+
public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) {
mNetworkStats = networkStats;
return this;
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 1174b685e92c..bf7326a5b30e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -20,10 +20,11 @@ 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.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
-import static android.window.TaskFragmentOrganizer.getTransitionType;
import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
@@ -76,6 +77,7 @@ import androidx.annotation.Nullable;
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.extensions.WindowExtensionsProvider;
+import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.android.internal.annotations.VisibleForTesting;
@@ -100,6 +102,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
final SplitPresenter mPresenter;
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ final TransactionManager mTransactionManager;
+
// Currently applied split configuration.
@GuardedBy("mLock")
private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
@@ -150,6 +156,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final MainThreadExecutor executor = new MainThreadExecutor();
mHandler = executor.mHandler;
mPresenter = new SplitPresenter(executor, this);
+ mTransactionManager = new TransactionManager(mPresenter);
final ActivityThread activityThread = ActivityThread.currentActivityThread();
final Application application = activityThread.getApplication();
// Register a callback to be notified about activities being created.
@@ -167,7 +174,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void accept(List<CommonFoldingFeature> foldingFeatures) {
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
for (int i = 0; i < mTaskContainers.size(); i++) {
final TaskContainer taskContainer = mTaskContainers.valueAt(i);
if (!taskContainer.isVisible()) {
@@ -186,7 +195,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
updateContainersInTask(wct, taskContainer);
updateAnimationOverride(taskContainer);
}
- mPresenter.applyTransaction(wct);
+ // The WCT should be applied and merged to the device state change transition if
+ // there is one.
+ transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
}
@@ -256,7 +267,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(
+ transaction.getTransactionToken());
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
for (TaskFragmentTransaction.Change change : changes) {
final int taskId = change.getTaskId();
@@ -307,8 +320,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Notify the server, and the server should apply and merge the
// WindowContainerTransaction to the active sync to finish the TaskFragmentTransaction.
- mPresenter.onTransactionHandled(transaction.getTransactionToken(), wct,
- getTransitionType(wct), false /* shouldApplyIndependently */);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
updateCallbackIfNecessary();
}
}
@@ -333,6 +345,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
container.setInfo(wct, taskFragmentInfo);
if (container.isFinished()) {
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else {
// Update with the latest Task configuration.
@@ -368,15 +381,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Do not finish the dependents if the last activity is reparented to PiP.
// Instead, the original split should be cleanup, and the dependent may be
// expanded to fullscreen.
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
cleanupForEnterPip(wct, container);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (taskFragmentInfo.isTaskClearedForReuse()) {
// Do not finish the dependents if this TaskFragment was cleared due to
// launching activity in the Task.
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (!container.isWaitingActivityAppear()) {
// Do not finish the container before the expected activity appear until
// timeout.
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
}
} else if (wasInPip && isInPip) {
@@ -571,6 +587,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
container.setInfo(wct, taskFragmentInfo);
container.clearPendingAppearedActivities();
if (container.isEmpty()) {
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
break;
@@ -1009,11 +1026,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
@GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- onTaskFragmentAppearEmptyTimeout(wct, container);
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ onTaskFragmentAppearEmptyTimeout(transactionRecord.getTransaction(), container);
// Can be applied independently as a timeout callback.
- mPresenter.applyTransaction(wct, getTransitionType(wct),
- true /* shouldApplyIndependently */);
+ transactionRecord.apply(true /* shouldApplyIndependently */);
}
/**
@@ -1023,6 +1039,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
}
@@ -1562,6 +1579,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @param isOnCreated whether this happens during the primary activity onCreated.
*/
@VisibleForTesting
+ @GuardedBy("mLock")
@Nullable
Bundle getPlaceholderOptions(@NonNull Activity primaryActivity, boolean isOnCreated) {
// Setting avoid move to front will also skip the animation. We only want to do that when
@@ -1569,6 +1587,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Check if the primary is resumed or if this is called when the primary is onCreated
// (not resumed yet).
if (isOnCreated || primaryActivity.isResumed()) {
+ // Only set trigger type if the launch happens in foreground.
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_OPEN);
return null;
}
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1595,6 +1615,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (SplitPresenter.shouldShowSplit(splitAttributes)) {
return false;
}
+
+ mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
false /* shouldFinishDependent */);
return true;
@@ -1905,23 +1927,26 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// that we don't launch it if an activity itself already requested something to be
// launched to side.
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- SplitController.this.onActivityCreated(wct, activity);
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_OPEN);
+ SplitController.this.onActivityCreated(transactionRecord.getTransaction(),
+ activity);
// The WCT should be applied and merged to the activity launch transition.
- mPresenter.applyTransaction(wct, getTransitionType(wct),
- false /* shouldApplyIndependently */);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
@Override
public void onActivityConfigurationChanged(@NonNull Activity activity) {
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- SplitController.this.onActivityConfigurationChanged(wct, activity);
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ SplitController.this.onActivityConfigurationChanged(
+ transactionRecord.getTransaction(), activity);
// The WCT should be applied and merged to the Task change transition so that the
// placeholder is launched in the same transition.
- mPresenter.applyTransaction(wct, getTransitionType(wct),
- false /* shouldApplyIndependently */);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
@@ -1977,7 +2002,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
synchronized (mLock) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_OPEN);
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
final int taskId = getTaskId(launchingActivity);
@@ -1990,13 +2018,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (launchedInTaskFragment != null) {
// Make sure the WCT is applied immediately instead of being queued so that the
// TaskFragment will be ready before activity attachment.
- mPresenter.applyTransaction(wct, getTransitionType(wct),
- false /* shouldApplyIndependently */);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
// Amend the request to let the WM know that the activity should be placed in
// the dedicated container.
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
launchedInTaskFragment.getTaskFragmentToken());
mCurrentIntent = intent;
+ } else {
+ transactionRecord.abort();
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
new file mode 100644
index 000000000000..0071fea41aa8
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.extensions.embedding;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_NONE;
+
+import android.os.IBinder;
+import android.view.WindowManager.TransitionType;
+import android.window.TaskFragmentOrganizer;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Responsible for managing the current {@link WindowContainerTransaction} as a response to device
+ * state changes and app interactions.
+ *
+ * A typical use flow:
+ * 1. Call {@link #startNewTransaction} to start tracking the changes.
+ * 2. Use {@link TransactionRecord#setOriginType(int)} (int)} to record the type of operation that
+ * will start a new transition on system server.
+ * 3. Use {@link #getCurrentTransactionRecord()} to get current {@link TransactionRecord} for
+ * changes.
+ * 4. Call {@link TransactionRecord#apply(boolean)} to request the system server to apply changes in
+ * the current {@link WindowContainerTransaction}, or call {@link TransactionRecord#abort()} to
+ * dispose the current one.
+ *
+ * Note:
+ * There should be only one transaction at a time. The caller should not call
+ * {@link #startNewTransaction} again before calling {@link TransactionRecord#apply(boolean)} or
+ * {@link TransactionRecord#abort()} to the previous transaction.
+ */
+class TransactionManager {
+
+ @NonNull
+ private final TaskFragmentOrganizer mOrganizer;
+
+ @Nullable
+ private TransactionRecord mCurrentTransaction;
+
+ TransactionManager(@NonNull TaskFragmentOrganizer organizer) {
+ mOrganizer = organizer;
+ }
+
+ @NonNull
+ TransactionRecord startNewTransaction() {
+ return startNewTransaction(null /* taskFragmentTransactionToken */);
+ }
+
+ /**
+ * Starts tracking the changes in a new {@link WindowContainerTransaction}. Caller can call
+ * {@link #getCurrentTransactionRecord()} later to continue adding change to the current
+ * transaction until {@link TransactionRecord#apply(boolean)} or
+ * {@link TransactionRecord#abort()} is called.
+ * @param taskFragmentTransactionToken {@link android.window.TaskFragmentTransaction
+ * #getTransactionToken()} if this is a response to a
+ * {@link android.window.TaskFragmentTransaction}.
+ */
+ @NonNull
+ TransactionRecord startNewTransaction(@Nullable IBinder taskFragmentTransactionToken) {
+ if (mCurrentTransaction != null) {
+ mCurrentTransaction = null;
+ throw new IllegalStateException(
+ "The previous transaction has not been applied or aborted,");
+ }
+ mCurrentTransaction = new TransactionRecord(taskFragmentTransactionToken);
+ return mCurrentTransaction;
+ }
+
+ /**
+ * Gets the current {@link TransactionRecord} started from {@link #startNewTransaction}.
+ */
+ @NonNull
+ TransactionRecord getCurrentTransactionRecord() {
+ if (mCurrentTransaction == null) {
+ throw new IllegalStateException("startNewTransaction() is not invoked before calling"
+ + " getCurrentTransactionRecord().");
+ }
+ return mCurrentTransaction;
+ }
+
+ /** The current transaction. The manager should only handle one transaction at a time. */
+ class TransactionRecord {
+ /**
+ * {@link WindowContainerTransaction} containing the current change.
+ * @see #startNewTransaction(IBinder)
+ * @see #apply (boolean)
+ */
+ @NonNull
+ private final WindowContainerTransaction mTransaction = new WindowContainerTransaction();
+
+ /**
+ * If the current transaction is a response to a
+ * {@link android.window.TaskFragmentTransaction}, this is the
+ * {@link android.window.TaskFragmentTransaction#getTransactionToken()}.
+ * @see #startNewTransaction(IBinder)
+ */
+ @Nullable
+ private final IBinder mTaskFragmentTransactionToken;
+
+ /**
+ * To track of the origin type of the current {@link #mTransaction}. When
+ * {@link #apply (boolean)} to start a new transition, this is the type to request.
+ * @see #setOriginType(int)
+ * @see #getTransactionTransitionType()
+ */
+ @TransitionType
+ private int mOriginType = TRANSIT_NONE;
+
+ TransactionRecord(@Nullable IBinder taskFragmentTransactionToken) {
+ mTaskFragmentTransactionToken = taskFragmentTransactionToken;
+ }
+
+ @NonNull
+ WindowContainerTransaction getTransaction() {
+ ensureCurrentTransaction();
+ return mTransaction;
+ }
+
+ /**
+ * Sets the {@link TransitionType} that triggers this transaction. If there are multiple
+ * calls, only the first call will be respected as the "origin" type.
+ */
+ void setOriginType(@TransitionType int type) {
+ ensureCurrentTransaction();
+ if (mOriginType != TRANSIT_NONE) {
+ // Skip if the origin type has already been set.
+ return;
+ }
+ mOriginType = type;
+ }
+
+ /**
+ * Requests the system server to apply the current transaction started from
+ * {@link #startNewTransaction}.
+ * @param shouldApplyIndependently If {@code true}, the {@link #mCurrentTransaction} will
+ * request a new transition, which will be queued until the
+ * sync engine is free if there is any other active sync.
+ * If {@code false}, the {@link #startNewTransaction} will
+ * be directly applied to the active sync.
+ */
+ void apply(boolean shouldApplyIndependently) {
+ ensureCurrentTransaction();
+ if (mTaskFragmentTransactionToken != null) {
+ // If this is a response to a TaskFragmentTransaction.
+ mOrganizer.onTransactionHandled(mTaskFragmentTransactionToken, mTransaction,
+ getTransactionTransitionType(), shouldApplyIndependently);
+ } else {
+ mOrganizer.applyTransaction(mTransaction, getTransactionTransitionType(),
+ shouldApplyIndependently);
+ }
+ dispose();
+ }
+
+ /** Called when there is no need to {@link #apply(boolean)} the current transaction. */
+ void abort() {
+ ensureCurrentTransaction();
+ dispose();
+ }
+
+ private void dispose() {
+ TransactionManager.this.mCurrentTransaction = null;
+ }
+
+ private void ensureCurrentTransaction() {
+ if (TransactionManager.this.mCurrentTransaction != this) {
+ throw new IllegalStateException(
+ "This transaction has already been apply() or abort().");
+ }
+ }
+
+ /**
+ * Gets the {@link TransitionType} that we will request transition with for the
+ * current {@link WindowContainerTransaction}.
+ */
+ @VisibleForTesting
+ @TransitionType
+ int getTransactionTransitionType() {
+ // Use TRANSIT_CHANGE as default if there is not opening/closing window.
+ return mOriginType != TRANSIT_NONE ? mOriginType : TRANSIT_CHANGE;
+ }
+ }
+}
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 25d034756265..a40303150079 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
@@ -132,6 +132,7 @@ public class SplitControllerTest {
private SplitController mSplitController;
private SplitPresenter mSplitPresenter;
+ private TransactionManager mTransactionManager;
@Before
public void setUp() {
@@ -140,8 +141,10 @@ public class SplitControllerTest {
.getCurrentWindowLayoutInfo(anyInt(), any());
mSplitController = new SplitController(mWindowLayoutComponent);
mSplitPresenter = mSplitController.mPresenter;
+ mTransactionManager = mSplitController.mTransactionManager;
spyOn(mSplitController);
spyOn(mSplitPresenter);
+ spyOn(mTransactionManager);
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
@@ -212,6 +215,8 @@ public class SplitControllerTest {
@Test
public void testOnTaskFragmentAppearEmptyTimeout() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any());
mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
@@ -615,6 +620,8 @@ public class SplitControllerTest {
@Test
public void testResolveActivityToContainer_placeholderRule_notInTaskFragment() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
setupPlaceholderRule(mActivity);
final SplitPlaceholderRule placeholderRule =
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -647,6 +654,8 @@ public class SplitControllerTest {
@Test
public void testResolveActivityToContainer_placeholderRule_inTopMostTaskFragment() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
setupPlaceholderRule(mActivity);
final SplitPlaceholderRule placeholderRule =
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -679,6 +688,8 @@ public class SplitControllerTest {
@Test
public void testResolveActivityToContainer_placeholderRule_inSecondarySplit() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
setupPlaceholderRule(mActivity);
final SplitPlaceholderRule placeholderRule =
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
@@ -961,6 +972,8 @@ public class SplitControllerTest {
@Test
public void testGetPlaceholderOptions() {
+ // Setup to make sure a transaction record is started.
+ mTransactionManager.startNewTransaction();
doReturn(true).when(mActivity).isResumed();
assertNull(mSplitController.getPlaceholderOptions(mActivity, false /* isOnCreated */));
@@ -1147,8 +1160,6 @@ public class SplitControllerTest {
+ "of other properties",
SplitController.haveSamePresentation(splitRule1, splitRule2,
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
-
-
}
/** Creates a mock activity in the organizer process. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java
new file mode 100644
index 000000000000..62006bd51399
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TransactionManagerTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.clearInvocations;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.window.TaskFragmentOrganizer;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link TransactionManager}.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:TransactionManagerTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TransactionManagerTest {
+
+ @Mock
+ private TaskFragmentOrganizer mOrganizer;
+ private TransactionManager mTransactionManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTransactionManager = new TransactionManager(mOrganizer);
+ }
+
+ @Test
+ public void testStartNewTransaction() {
+ mTransactionManager.startNewTransaction();
+
+ // Throw exception if #startNewTransaction is called twice without #apply() or #abort().
+ assertThrows(IllegalStateException.class, mTransactionManager::startNewTransaction);
+
+ // Allow to start new after #apply() the last transaction.
+ TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ transactionRecord = mTransactionManager.startNewTransaction();
+
+ // Allow to start new after #abort() the last transaction.
+ transactionRecord.abort();
+ mTransactionManager.startNewTransaction();
+ }
+
+ @Test
+ public void testSetTransactionOriginType() {
+ // Return TRANSIT_CHANGE if there is no trigger type set.
+ TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+
+ assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType());
+
+ // Return the first set type.
+ mTransactionManager.getCurrentTransactionRecord().abort();
+ transactionRecord = mTransactionManager.startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_OPEN);
+
+ assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
+
+ transactionRecord.setOriginType(TRANSIT_CLOSE);
+
+ assertEquals(TRANSIT_OPEN, transactionRecord.getTransactionTransitionType());
+
+ // Reset when #startNewTransaction().
+ transactionRecord.abort();
+ transactionRecord = mTransactionManager.startNewTransaction();
+
+ assertEquals(TRANSIT_CHANGE, transactionRecord.getTransactionTransitionType());
+ }
+
+ @Test
+ public void testGetCurrentTransactionRecord() {
+ // Throw exception if #getTransaction is called without calling #startNewTransaction().
+ assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
+
+ TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ assertNotNull(transactionRecord);
+
+ // Same WindowContainerTransaction should be returned.
+ assertSame(transactionRecord, mTransactionManager.getCurrentTransactionRecord());
+
+ // Reset after #abort().
+ transactionRecord.abort();
+ assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
+
+ // New WindowContainerTransaction after #startNewTransaction().
+ mTransactionManager.startNewTransaction();
+ assertNotEquals(transactionRecord, mTransactionManager.getCurrentTransactionRecord());
+
+ // Reset after #apply().
+ mTransactionManager.getCurrentTransactionRecord().apply(
+ false /* shouldApplyIndependently */);
+ assertThrows(IllegalStateException.class, mTransactionManager::getCurrentTransactionRecord);
+ }
+
+ @Test
+ public void testApply() {
+ // #applyTransaction(false)
+ TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ int transitionType = transactionRecord.getTransactionTransitionType();
+ WindowContainerTransaction wct = transactionRecord.getTransaction();
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+
+ verify(mOrganizer).applyTransaction(wct, transitionType,
+ false /* shouldApplyIndependently */);
+
+ // #applyTransaction(true)
+ clearInvocations(mOrganizer);
+ transactionRecord = mTransactionManager.startNewTransaction();
+ transitionType = transactionRecord.getTransactionTransitionType();
+ wct = transactionRecord.getTransaction();
+ transactionRecord.apply(true /* shouldApplyIndependently */);
+
+ verify(mOrganizer).applyTransaction(wct, transitionType,
+ true /* shouldApplyIndependently */);
+
+ // #onTransactionHandled(false)
+ clearInvocations(mOrganizer);
+ IBinder token = new Binder();
+ transactionRecord = mTransactionManager.startNewTransaction(token);
+ transitionType = transactionRecord.getTransactionTransitionType();
+ wct = transactionRecord.getTransaction();
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+
+ verify(mOrganizer).onTransactionHandled(token, wct, transitionType,
+ false /* shouldApplyIndependently */);
+
+ // #onTransactionHandled(true)
+ clearInvocations(mOrganizer);
+ token = new Binder();
+ transactionRecord = mTransactionManager.startNewTransaction(token);
+ transitionType = transactionRecord.getTransactionTransitionType();
+ wct = transactionRecord.getTransaction();
+ transactionRecord.apply(true /* shouldApplyIndependently */);
+
+ verify(mOrganizer).onTransactionHandled(token, wct, transitionType,
+ true /* shouldApplyIndependently */);
+
+ // Throw exception if there is any more interaction.
+ final TransactionRecord record = transactionRecord;
+ assertThrows(IllegalStateException.class,
+ () -> record.apply(false /* shouldApplyIndependently */));
+ assertThrows(IllegalStateException.class,
+ () -> record.apply(true /* shouldApplyIndependently */));
+ assertThrows(IllegalStateException.class,
+ record::abort);
+ }
+
+ @Test
+ public void testAbort() {
+ final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+ transactionRecord.abort();
+
+ // Throw exception if there is any more interaction.
+ verifyNoMoreInteractions(mOrganizer);
+ assertThrows(IllegalStateException.class,
+ () -> transactionRecord.apply(false /* shouldApplyIndependently */));
+ assertThrows(IllegalStateException.class,
+ () -> transactionRecord.apply(true /* shouldApplyIndependently */));
+ assertThrows(IllegalStateException.class,
+ transactionRecord::abort);
+ }
+}
diff --git a/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml
index 66e5b43d76af..5ecba380fb60 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml
@@ -22,8 +22,8 @@
>
<group android:scaleX="0.5"
android:scaleY="0.5"
- android:translateX="8.0"
- android:translateY="8.0" >
+ android:translateX="4.0"
+ android:translateY="4.0" >
<path
android:fillColor="@android:color/black"
android:pathData="MM24,40.3 L7.7,24 24,7.7 26.8,10.45 15.3,22H40.3V26H15.3L26.8,37.5Z"/>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
index 53a8bb18537c..416287d2cbb3 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
@@ -17,4 +17,5 @@
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/white" />
+ <corners android:radius="20dp" />
</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
index 851cbf26afc3..cf9e632f6941 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
@@ -21,8 +21,8 @@
android:viewportHeight="32.0">
<group android:scaleX="0.5"
android:scaleY="0.5"
- android:translateX="8.0"
- android:translateY="8.0" >
+ android:translateX="4.0"
+ android:translateY="4.0" >
<path
android:fillColor="@android:color/black"
android:pathData="M12.45,38.35 L9.65,35.55 21.2,24 9.65,12.45 12.45,9.65 24,21.2 35.55,9.65 38.35,12.45 26.8,24 38.35,35.55 35.55,38.35 24,26.8Z"/>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index ee0f4663c940..c9f262398f68 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -18,6 +18,8 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
- <path
- android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
+ <group android:translateY="8.0">
+ <path
+ android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
+ </group>
</vector>
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index cc600741d1b7..f9d153fd7408 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimeer"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Maak klein"</string>
<string name="close_button_text" msgid="2913281996024033299">"Maak toe"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handvatsel"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 7aa877041d53..17b1d1bdbff1 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"አሳንስ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ተመለስ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"መያዣ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 2d0d9705cab1..2babc3ea5fcd 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"تكبير"</string>
<string name="minimize_button_text" msgid="271592547935841753">"تصغير"</string>
<string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"رجوع"</string>
+ <string name="handle_text" msgid="1766582106752184456">"مقبض"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 42153ff1ad96..fe43cd69110f 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -37,14 +37,14 @@
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"বাওঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
- <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"বাওঁফালৰ স্ক্ৰীণখন ৭০% কৰক"</string>
- <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"বাওঁফালৰ স্ক্ৰীণখন ৫০% কৰক"</string>
- <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"বাওঁফালৰ স্ক্ৰীণখন ৩০% কৰক"</string>
+ <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"বাওঁফালৰ স্ক্ৰীনখন ৭০% কৰক"</string>
+ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"বাওঁফালৰ স্ক্ৰীনখন ৫০% কৰক"</string>
+ <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"বাওঁফালৰ স্ক্ৰীনখন ৩০% কৰক"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"সোঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"শীৰ্ষ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
- <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"শীর্ষ স্ক্ৰীণখন ৭০% কৰক"</string>
- <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ স্ক্ৰীণখন ৫০% কৰক"</string>
- <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ স্ক্ৰীণখন ৩০% কৰক"</string>
+ <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"শীর্ষ স্ক্ৰীনখন ৭০% কৰক"</string>
+ <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ স্ক্ৰীনখন ৫০% কৰক"</string>
+ <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ স্ক্ৰীনখন ৩০% কৰক"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"তলৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"এখন হাতেৰে ব্যৱহাৰ কৰা ম’ড ব্যৱহাৰ কৰা"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"বাহিৰ হ’বলৈ স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ ছোৱাইপ কৰক অথবা এপ্‌টোৰ ওপৰত যিকোনো ঠাইত টিপক"</string>
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string>
<string name="minimize_button_text" msgid="271592547935841753">"মিনিমাইজ কৰক"</string>
<string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"উভতি যাওক"</string>
+ <string name="handle_text" msgid="1766582106752184456">"হেণ্ডেল"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 5da5a97ff076..c22f5425048e 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Böyüdün"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Kiçildin"</string>
<string name="close_button_text" msgid="2913281996024033299">"Bağlayın"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Geriyə"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Hər kəsə açıq istifadəçi adı"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index aff2b9f686c8..4dda1dbf3906 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Uvećajte"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Umanjite"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index e325e511596c..cf5394b0ebd2 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Згарнуць"</string>
<string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 3bedc86a79ac..c525f521c0e3 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Намаляване"</string>
<string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Манипулатор"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 35e5b38233cc..b652d383a49a 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ছোট করুন"</string>
<string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ফিরে যান"</string>
+ <string name="handle_text" msgid="1766582106752184456">"হাতল"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 34b282b24666..bec284d4dff8 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziranje"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimiziranje"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zatvaranje"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 0279917272a5..c84bb4884068 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximitza"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimitza"</string>
<string name="close_button_text" msgid="2913281996024033299">"Tanca"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Enrere"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Ansa"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index d58100b5bb9b..17f7374d9d00 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovat"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimalizovat"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zavřít"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Zpět"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Úchyt"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index abb9cea6c6f3..60c1f8588445 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimér"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
<string name="close_button_text" msgid="2913281996024033299">"Luk"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Tilbage"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Håndtag"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index a579394818b9..b57f0c857d26 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximieren"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimieren"</string>
<string name="close_button_text" msgid="2913281996024033299">"Schließen"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Zurück"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Ziehpunkt"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 248cff818da6..2f929466343c 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Ελαχιστοποίηση"</string>
<string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Πίσω"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Λαβή"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 9c798b84c814..9c88e78616e7 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 9c798b84c814..9c88e78616e7 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 9c798b84c814..9c88e78616e7 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 9c798b84c814..9c88e78616e7 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Back"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 5ff5c586b38f..646b3c545c00 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎Maximize‎‏‎‎‏‎"</string>
<string name="minimize_button_text" msgid="271592547935841753">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‎‏‎Minimize‎‏‎‎‏‎"</string>
<string name="close_button_text" msgid="2913281996024033299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎Close‎‏‎‎‏‎"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‏‎‏‎Back‎‏‎‎‏‎"</string>
+ <string name="handle_text" msgid="1766582106752184456">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‎‎‎Handle‎‏‎‎‏‎"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 08a7f49f3dfd..aed03acf026c 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 139232cc808f..bddc2c3e61bc 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index d6f35807d290..696a520d0b86 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimeeri"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimeeri"</string>
<string name="close_button_text" msgid="2913281996024033299">"Sule"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Tagasi"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Käepide"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index e0efadd6f735..d1fca9b6d4b9 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizatu"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizatu"</string>
<string name="close_button_text" msgid="2913281996024033299">"Itxi"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Atzera"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Kontu-izena"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 267dd2050cfa..7550a091bd79 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string>
<string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string>
<string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"برگشتن"</string>
+ <string name="handle_text" msgid="1766582106752184456">"دستگیره"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 30321a20c643..a79adf8e686c 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Suurenna"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Pienennä"</string>
<string name="close_button_text" msgid="2913281996024033299">"Sulje"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Takaisin"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Kahva"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 12789975fb5e..4c59b9999d76 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Identifiant"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 84c861896443..865e2dcf4775 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Poignée"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 59ec2deab17c..47e6696dfe4c 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Pechar"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 85d98ed2fc7b..5d52c58616a8 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string>
<string name="minimize_button_text" msgid="271592547935841753">"નાનું કરો"</string>
<string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"પાછળ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"હૅન્ડલ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index bd90b7ea9220..2c643ded9dee 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string>
<string name="minimize_button_text" msgid="271592547935841753">"विंडो छोटी करें"</string>
<string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"वापस जाएं"</string>
+ <string name="handle_text" msgid="1766582106752184456">"हैंडल"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 41df0df1ee87..32ab87cfda64 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimiziraj"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Natrag"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Pokazivač"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 9736a69f4001..dba7f4e37f8e 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Teljes méret"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Kis méret"</string>
<string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Vissza"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Fogópont"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 3c6449421dec..d3bca3374e9d 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Ծալել"</string>
<string name="close_button_text" msgid="2913281996024033299">"Փակել"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Հետ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Նշիչ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 61f39036320c..f157fcf6dbec 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimalkan"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimalkan"</string>
<string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Tuas"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 044348c94a95..ead1757ed4bb 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minnka"</string>
<string name="close_button_text" msgid="2913281996024033299">"Loka"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Til baka"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handfang"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index ca97693481cb..4e8ac62a6288 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Ingrandisci"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Riduci a icona"</string>
<string name="close_button_text" msgid="2913281996024033299">"Chiudi"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Indietro"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 67de8a0fde34..12c962d09ce1 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string>
<string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string>
<string name="close_button_text" msgid="2913281996024033299">"סגירה"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"חזרה"</string>
+ <string name="handle_text" msgid="1766582106752184456">"נקודת אחיזה"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index cc8527e73636..d9f514a4c312 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
<string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
<string name="close_button_text" msgid="2913281996024033299">"閉じる"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"戻る"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ハンドル"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 0ef4563b7a8a..b28436131fe9 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ჩაკეცვა"</string>
<string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"უკან"</string>
+ <string name="handle_text" msgid="1766582106752184456">"იდენტიფიკატორი"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 6ba9a68943cf..2d842b88996a 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Жаю"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Кішірейту"</string>
<string name="close_button_text" msgid="2913281996024033299">"Жабу"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Артқа"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 03682d800e50..3243a648ac0e 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string>
<string name="minimize_button_text" msgid="271592547935841753">"បង្រួម"</string>
<string name="close_button_text" msgid="2913281996024033299">"បិទ"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ថយក្រោយ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ឈ្មោះអ្នកប្រើប្រាស់"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 167a38e09c60..57c7124222c9 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ಕುಗ್ಗಿಸಿ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ಹಿಂದಕ್ಕೆ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ಹ್ಯಾಂಡಲ್"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 1175a0e884a4..3a655b870df3 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"최대화"</string>
<string name="minimize_button_text" msgid="271592547935841753">"최소화"</string>
<string name="close_button_text" msgid="2913281996024033299">"닫기"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"뒤로"</string>
+ <string name="handle_text" msgid="1766582106752184456">"핸들"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index b047863bdcf6..ab51ca0725b0 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Кичирейтүү"</string>
<string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Артка"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 00bf7f4c7983..b800e3eb174f 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ຫຍໍ້ລົງ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ປິດ"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ກັບຄືນ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ມືບັງຄັບ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 39817930f308..94339a46df6f 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Padidinti"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Sumažinti"</string>
<string name="close_button_text" msgid="2913281996024033299">"Uždaryti"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Atgal"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Rankenėlė"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 3599c14dcadd..d28245360922 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimizēt"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizēt"</string>
<string name="close_button_text" msgid="2913281996024033299">"Aizvērt"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Atpakaļ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Turis"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 6c41b0ca0afd..64ce3f617768 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Минимизирај"</string>
<string name="close_button_text" msgid="2913281996024033299">"Затвори"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Прекар"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 76dfc0d944a5..5db8d6d071f1 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ചെറുതാക്കുക"</string>
<string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"മടങ്ങുക"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ഹാൻഡിൽ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index a8bd85ed27f5..be02be111438 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Багасгах"</string>
<string name="close_button_text" msgid="2913281996024033299">"Хаах"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Буцах"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Бариул"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 5874812d9281..779cf5c870bc 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string>
<string name="minimize_button_text" msgid="271592547935841753">"लहान करा"</string>
<string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"मागे जा"</string>
+ <string name="handle_text" msgid="1766582106752184456">"हँडल"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 88270df81660..85d380e04889 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimumkan"</string>
<string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Pemegang"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 9a2d1bad094e..517098d76d4a 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -66,7 +66,7 @@
<string name="bubbles_user_education_description" msgid="4215862563054175407">"စကားဝိုင်းအသစ်များကို မျောနေသည့် သင်္ကေတများ သို့မဟုတ် ပူဖောင်းကွက်များအဖြစ် မြင်ရပါမည်။ ပူဖောင်းကွက်ကိုဖွင့်ရန် တို့ပါ။ ရွှေ့ရန် ၎င်းကို ဖိဆွဲပါ။"</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"ပူဖောင်းကွက်ကို အချိန်မရွေး ထိန်းချုပ်ရန်"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"ဤအက်ပ်မှနေ၍ ပူဖောင်းများကို ပိတ်ရန်အတွက် \'စီမံရန်\' ကို တို့ပါ"</string>
- <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"ရပြီ"</string>
+ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"နားလည်ပြီ"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"လတ်တလော ပူဖောင်းကွက်များ မရှိပါ"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"လတ်တလော ပူဖောင်းကွက်များနှင့် ပိတ်လိုက်သော ပူဖောင်းကွက်များကို ဤနေရာတွင် မြင်ရပါမည်"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"ပူဖောင်းဖောက်သံ"</string>
@@ -79,9 +79,11 @@
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"ကြည့်ပြီး ပိုမိုလုပ်ဆောင်ပါ"</string>
<string name="letterbox_education_split_screen_text" msgid="6206339484068670830">"မျက်နှာပြင် ခွဲ၍ပြသနိုင်ရန် နောက်အက်ပ်တစ်ခုကို ဖိဆွဲပါ"</string>
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"နေရာပြန်ချရန် အက်ပ်အပြင်ဘက်ကို နှစ်ချက်တို့ပါ"</string>
- <string name="letterbox_education_got_it" msgid="4057634570866051177">"ရပြီ"</string>
+ <string name="letterbox_education_got_it" msgid="4057634570866051177">"နားလည်ပြီ"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ချုံ့ရန်"</string>
<string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"နောက်သို့"</string>
+ <string name="handle_text" msgid="1766582106752184456">"သုံးသူအမည်"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index ec9e635dd9bb..0ceee2d1c928 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimer"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
<string name="close_button_text" msgid="2913281996024033299">"Lukk"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Tilbake"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Håndtak"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index dc7d98d1f702..7ba49e57111e 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string>
<string name="minimize_button_text" msgid="271592547935841753">"मिनिमाइज गर्नुहोस्"</string>
<string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"पछाडि"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ह्यान्डल"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 5bae2a34100c..dd3ebe415218 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimaliseren"</string>
<string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Gebruikersnaam"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index c5ef4f10ce39..52280a1daf24 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ଛୋଟ କରନ୍ତୁ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ପଛକୁ ଫେରନ୍ତୁ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ହେଣ୍ଡେଲ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index f0262052513c..c3056ca0a511 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ਛੋਟਾ ਕਰੋ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ਪਿੱਛੇ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ਹੈਂਡਲ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 22c0c37002c9..56a6fb1664c1 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maksymalizuj"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimalizuj"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Wstecz"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Uchwyt"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 0bbffb3182b5..3cb708dda567 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Alça"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 07dd4d73ebd3..e5be57848957 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Anterior"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Indicador"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 0bbffb3182b5..3cb708dda567 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Alça"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 0aae6a509bdb..c03e043ee6f0 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizează"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizează"</string>
<string name="close_button_text" msgid="2913281996024033299">"Închide"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Înapoi"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Ghidaj"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index f1ff000ebc4c..b96caf23a0a7 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Свернуть"</string>
<string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 39bd260c2901..a1ec3b5d5bb2 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string>
<string name="minimize_button_text" msgid="271592547935841753">"කුඩා කරන්න"</string>
<string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"ආපසු"</string>
+ <string name="handle_text" msgid="1766582106752184456">"හැඬලය"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 88592315a53c..e3dafb6c88e9 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovať"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimalizovať"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zavrieť"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Späť"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Rukoväť"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 22fe0f8bc657..2f995e5076f3 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimiraj"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zapri"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Nazaj"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Ročica"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index 2a3671b85d0d..3d9bde4f8066 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimizo"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizo"</string>
<string name="close_button_text" msgid="2913281996024033299">"Mbyll"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Pas"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Emërtimi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 09471037fde7..b00c4f49f649 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Умањите"</string>
<string name="close_button_text" msgid="2913281996024033299">"Затворите"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 70282a4b1a4d..b39fd04ebf3d 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Utöka"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimera"</string>
<string name="close_button_text" msgid="2913281996024033299">"Stäng"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Tillbaka"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handtag"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 1aab85c54add..f4d4ceec5a1f 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Panua"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Punguza"</string>
<string name="close_button_text" msgid="2913281996024033299">"Funga"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Rudi nyuma"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Ncha"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index befc8eb6f808..6d050c2ba26e 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string>
<string name="minimize_button_text" msgid="271592547935841753">"சிறிதாக்கும்"</string>
<string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"பின்செல்லும்"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ஹேண்டில்"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index a3e21f745707..91c411f0d49c 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"గరిష్టీకరించండి"</string>
<string name="minimize_button_text" msgid="271592547935841753">"కుదించండి"</string>
<string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"వెనుకకు"</string>
+ <string name="handle_text" msgid="1766582106752184456">"హ్యాండిల్"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 63d3bb83832d..ee362dcedd0a 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ย่อ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ปิด"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"กลับ"</string>
+ <string name="handle_text" msgid="1766582106752184456">"แฮนเดิล"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 50334f5e772e..d82036568499 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"I-maximize"</string>
<string name="minimize_button_text" msgid="271592547935841753">"I-minimize"</string>
<string name="close_button_text" msgid="2913281996024033299">"Isara"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Bumalik"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Handle"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index e1ea0df780e6..025e2e6215e7 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Ekranı Kapla"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Küçült"</string>
<string name="close_button_text" msgid="2913281996024033299">"Kapat"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Geri"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Herkese açık kullanıcı adı"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 9e713c77cacd..97bb68080117 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Згорнути"</string>
<string name="close_button_text" msgid="2913281996024033299">"Закрити"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 596cf4b15d40..883026a437a5 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"بڑا کریں"</string>
<string name="minimize_button_text" msgid="271592547935841753">"چھوٹا کریں"</string>
<string name="close_button_text" msgid="2913281996024033299">"بند کریں"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"پیچھے"</string>
+ <string name="handle_text" msgid="1766582106752184456">"ہینڈل"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 275940a22f25..ce73bd586c71 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Kichraytirish"</string>
<string name="close_button_text" msgid="2913281996024033299">"Yopish"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Orqaga"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 24fb1ca57aec..511db6fbc116 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Thu nhỏ"</string>
<string name="close_button_text" msgid="2913281996024033299">"Đóng"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Quay lại"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Xử lý"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index e52a7b556be0..16cbf126eff8 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
<string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
<string name="close_button_text" msgid="2913281996024033299">"关闭"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"返回"</string>
+ <string name="handle_text" msgid="1766582106752184456">"处理"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index eed1e5299365..5a497d090f54 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
<string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
<string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"返去"</string>
+ <string name="handle_text" msgid="1766582106752184456">"控點"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index f9f28ab02aa6..2c2ce33f58ab 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
<string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
<string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"返回"</string>
+ <string name="handle_text" msgid="1766582106752184456">"控點"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index ddb6a4c3166e..59d87a0a9371 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -19,7 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="pip_phone_close" msgid="5783752637260411309">"Vala"</string>
<string name="pip_phone_expand" msgid="2579292903468287504">"Nweba"</string>
- <string name="pip_phone_settings" msgid="5468987116750491918">"Izilungiselelo"</string>
+ <string name="pip_phone_settings" msgid="5468987116750491918">"Amasethingi"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Faka ukuhlukanisa isikrini"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Imenyu"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"U-<xliff:g id="NAME">%s</xliff:g> ungaphakathi kwesithombe esiphakathi kwesithombe"</string>
@@ -84,4 +84,6 @@
<string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Nciphisa"</string>
<string name="close_button_text" msgid="2913281996024033299">"Vala"</string>
+ <string name="back_button_text" msgid="1469718707134137085">"Emuva"</string>
+ <string name="handle_text" msgid="1766582106752184456">"Isibambo"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 0bc70857a113..3ee20ea95ee5 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -321,4 +321,21 @@
<!-- The smaller size of the dismiss target (shrinks when something is in the target). -->
<dimen name="floating_dismiss_circle_small">120dp</dimen>
+
+ <!-- The thickness of shadows of a window that has focus in DIP. -->
+ <dimen name="freeform_decor_shadow_focused_thickness">20dp</dimen>
+
+ <!-- The thickness of shadows of a window that doesn't have focus in DIP. -->
+ <dimen name="freeform_decor_shadow_unfocused_thickness">5dp</dimen>
+
+ <!-- Height of button (32dp) + 2 * margin (5dp each). -->
+ <dimen name="freeform_decor_caption_height">42dp</dimen>
+
+ <!-- Width of buttons (64dp) + handle (128dp) + padding (24dp total). -->
+ <dimen name="freeform_decor_caption_width">216dp</dimen>
+
+ <dimen name="freeform_resize_handle">30dp</dimen>
+
+ <dimen name="freeform_resize_corner">44dp</dimen>
+
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 938189fc8a88..cbcd9498fe55 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
@@ -75,13 +75,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private static final String TAG = "BackAnimationController";
private static final int SETTING_VALUE_OFF = 0;
private static final int SETTING_VALUE_ON = 1;
- private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
- "persist.wm.debug.predictive_back_progress_threshold";
public static final boolean IS_ENABLED =
SystemProperties.getInt("persist.wm.debug.predictive_back",
- SETTING_VALUE_ON) != SETTING_VALUE_OFF;
- private static final int PROGRESS_THRESHOLD = SystemProperties
- .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
+ SETTING_VALUE_ON) == SETTING_VALUE_ON;
+ /** Flag for U animation features */
+ public static boolean IS_U_ANIMATION_ENABLED =
+ SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
+ SETTING_VALUE_OFF) == SETTING_VALUE_ON;
+ /** Predictive back animation developer option */
private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
// TODO (b/241808055) Find a appropriate time to remove during refactor
private static final boolean USE_TRANSITION =
@@ -114,7 +115,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@Nullable
private IOnBackInvokedCallback mBackToLauncherCallback;
private float mTriggerThreshold;
- private float mProgressThreshold;
private final Runnable mResetTransitionRunnable = () -> {
finishAnimation();
mTransitionInProgress = false;
@@ -125,7 +125,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private IBackNaviAnimationController mBackAnimationController;
private BackAnimationAdaptor mBackAnimationAdaptor;
- private boolean mWaitingAnimationStart;
private final TouchTracker mTouchTracker = new TouchTracker();
private final CachingBackDispatcher mCachingBackDispatcher = new CachingBackDispatcher();
@@ -148,44 +147,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
};
/**
- * Helper class to record the touch location for gesture start and latest.
- */
- private static class TouchTracker {
- /**
- * Location of the latest touch event
- */
- private float mLatestTouchX;
- private float mLatestTouchY;
- private int mSwipeEdge;
-
- /**
- * Location of the initial touch event of the back gesture.
- */
- private float mInitTouchX;
- private float mInitTouchY;
-
- void update(float touchX, float touchY, int swipeEdge) {
- mLatestTouchX = touchX;
- mLatestTouchY = touchY;
- mSwipeEdge = swipeEdge;
- }
-
- void setGestureStartLocation(float touchX, float touchY) {
- mInitTouchX = touchX;
- mInitTouchY = touchY;
- }
-
- int getDeltaFromGestureStart(float touchX) {
- return Math.round(touchX - mInitTouchX);
- }
-
- void reset() {
- mInitTouchX = 0;
- mInitTouchY = 0;
- }
- }
-
- /**
* Cache the temporary callback and trigger result if gesture was finish before received
* BackAnimationRunner#onAnimationStart/cancel, so there can continue play the animation.
*/
@@ -212,15 +173,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
boolean consumed = false;
if (mWaitingAnimation && mOnBackCallback != null) {
if (mTriggerBack) {
- final BackEvent backFinish = new BackEvent(
- mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 1,
- mTouchTracker.mSwipeEdge, mAnimationTarget);
+ final BackEvent backFinish = mTouchTracker.createProgressEvent(1);
dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
dispatchOnBackInvoked(mOnBackCallback);
} else {
- final BackEvent backFinish = new BackEvent(
- mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0,
- mTouchTracker.mSwipeEdge, mAnimationTarget);
+ final BackEvent backFinish = mTouchTracker.createProgressEvent(0);
dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
dispatchOnBackCancelled(mOnBackCallback);
}
@@ -263,6 +220,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
shellInit.addInitCallback(this::onInit, this);
}
+ @VisibleForTesting
+ void setEnableUAnimation(boolean enable) {
+ IS_U_ANIMATION_ENABLED = enable;
+ }
+
private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
@@ -404,7 +366,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
- mTouchTracker.update(touchX, touchY, swipeEdge);
+ mTouchTracker.update(touchX, touchY);
if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
mShouldStartOnNextMoveEvent = true;
@@ -414,7 +376,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
// Let the animation initialized here to make sure the onPointerDownOutsideFocus
// could be happened when ACTION_DOWN, it may change the current focus that we
// would access it when startBackNavigation.
- onGestureStarted(touchX, touchY);
+ onGestureStarted(touchX, touchY, swipeEdge);
mShouldStartOnNextMoveEvent = false;
}
onMove(touchX, touchY, swipeEdge);
@@ -428,14 +390,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
- private void onGestureStarted(float touchX, float touchY) {
+ private void onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
if (mBackGestureStarted || mBackNavigationInfo != null) {
Log.e(TAG, "Animation is being initialized but is already started.");
finishAnimation();
}
- mTouchTracker.setGestureStartLocation(touchX, touchY);
+ mTouchTracker.setGestureStartLocation(touchX, touchY, swipeEdge);
mBackGestureStarted = true;
try {
@@ -464,6 +426,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
displayTargetScreenshot(hardwareBuffer,
backNavigationInfo.getTaskWindowConfiguration());
}
+ targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
mTransaction.apply();
} else if (dispatchToLauncher) {
targetCallback = mBackToLauncherCallback;
@@ -474,7 +437,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
}
if (!USE_TRANSITION || !dispatchToLauncher) {
- dispatchOnBackStarted(targetCallback);
+ dispatchOnBackStarted(
+ targetCallback,
+ mTouchTracker.createStartEvent(
+ mBackNavigationInfo.getDepartingAnimationTarget()));
}
}
@@ -514,29 +480,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (!mBackGestureStarted || mBackNavigationInfo == null) {
return;
}
- int deltaX = mTouchTracker.getDeltaFromGestureStart(touchX);
- float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
- float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
- if (USE_TRANSITION) {
- if (mBackAnimationController != null && mAnimationTarget != null) {
- final BackEvent backEvent = new BackEvent(
- touchX, touchY, progress, swipeEdge, mAnimationTarget);
+ final BackEvent backEvent = mTouchTracker.createProgressEvent();
+ if (USE_TRANSITION && mBackAnimationController != null && mAnimationTarget != null) {
dispatchOnBackProgressed(mBackToLauncherCallback, backEvent);
- }
- } else {
+ } else if (mEnableAnimations.get()) {
int backType = mBackNavigationInfo.getType();
- RemoteAnimationTarget animationTarget =
- mBackNavigationInfo.getDepartingAnimationTarget();
-
- BackEvent backEvent = new BackEvent(
- touchX, touchY, progress, swipeEdge, animationTarget);
- IOnBackInvokedCallback targetCallback = null;
+ IOnBackInvokedCallback targetCallback;
if (shouldDispatchToLauncher(backType)) {
targetCallback = mBackToLauncherCallback;
- } else if (backType == BackNavigationInfo.TYPE_CROSS_TASK
- || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
- // TODO(208427216) Run the actual animation
- } else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
+ } else {
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
}
dispatchOnBackProgressed(targetCallback, backEvent);
@@ -620,18 +572,21 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
|| mBackNavigationInfo.getDepartingAnimationTarget() != null);
}
- private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) {
+ private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
+ BackEvent backEvent) {
if (callback == null) {
return;
}
try {
- callback.onBackStarted();
+ if (shouldDispatchAnimation(callback)) {
+ callback.onBackStarted(backEvent);
+ }
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackStarted error: ", e);
}
}
- private static void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
+ private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) {
if (callback == null) {
return;
}
@@ -642,29 +597,38 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
- private static void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
+ private void dispatchOnBackCancelled(IOnBackInvokedCallback callback) {
if (callback == null) {
return;
}
try {
- callback.onBackCancelled();
+ if (shouldDispatchAnimation(callback)) {
+ callback.onBackCancelled();
+ }
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackCancelled error: ", e);
}
}
- private static void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
+ private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
BackEvent backEvent) {
if (callback == null) {
return;
}
try {
- callback.onBackProgressed(backEvent);
+ if (shouldDispatchAnimation(callback)) {
+ callback.onBackProgressed(backEvent);
+ }
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackProgressed error: ", e);
}
}
+ private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
+ return (IS_U_ANIMATION_ENABLED || callback == mBackToLauncherCallback)
+ && mEnableAnimations.get();
+ }
+
/**
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/
@@ -673,10 +637,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
mTriggerBack = triggerBack;
+ mTouchTracker.setTriggerBack(triggerBack);
}
private void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
- mProgressThreshold = progressThreshold;
+ mTouchTracker.setProgressThreshold(progressThreshold);
mTriggerThreshold = triggerThreshold;
}
@@ -686,6 +651,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
boolean triggerBack = mTriggerBack;
mBackNavigationInfo = null;
+ mAnimationTarget = null;
mTriggerBack = false;
mShouldStartOnNextMoveEvent = false;
if (backNavigationInfo == null) {
@@ -762,17 +728,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
tx.apply();
}
}
- // TODO animation target should be passed at onBackStarted
- dispatchOnBackStarted(mBackToLauncherCallback);
- // TODO This is Workaround for LauncherBackAnimationController, there will need
- // to dispatch onBackProgressed twice(startBack & updateBackProgress) to
- // initialize the animation data, for now that would happen when onMove
- // called, but there will no expected animation if the down -> up gesture
- // happen very fast which ACTION_MOVE only happen once.
- final BackEvent backInit = new BackEvent(
- mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0,
- mTouchTracker.mSwipeEdge, mAnimationTarget);
- dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
+ dispatchOnBackStarted(mBackToLauncherCallback,
+ mTouchTracker.createStartEvent(mAnimationTarget));
+ final BackEvent backInit = mTouchTracker.createProgressEvent();
if (!mCachingBackDispatcher.consume()) {
dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
new file mode 100644
index 000000000000..ccfac65d6342
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import android.os.SystemProperties;
+import android.view.RemoteAnimationTarget;
+import android.window.BackEvent;
+
+/**
+ * Helper class to record the touch location for gesture and generate back events.
+ */
+class TouchTracker {
+ private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
+ "persist.wm.debug.predictive_back_progress_threshold";
+ private static final int PROGRESS_THRESHOLD = SystemProperties
+ .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
+ private float mProgressThreshold;
+ /**
+ * Location of the latest touch event
+ */
+ private float mLatestTouchX;
+ private float mLatestTouchY;
+ private boolean mTriggerBack;
+
+ /**
+ * Location of the initial touch event of the back gesture.
+ */
+ private float mInitTouchX;
+ private float mInitTouchY;
+ private float mStartThresholdX;
+ private int mSwipeEdge;
+ private boolean mCancelled;
+
+ void update(float touchX, float touchY) {
+ /**
+ * If back was previously cancelled but the user has started swiping in the forward
+ * direction again, restart back.
+ */
+ if (mCancelled && ((touchX > mLatestTouchX && mSwipeEdge == BackEvent.EDGE_LEFT)
+ || touchX < mLatestTouchX && mSwipeEdge == BackEvent.EDGE_RIGHT)) {
+ mCancelled = false;
+ mStartThresholdX = touchX;
+ }
+ mLatestTouchX = touchX;
+ mLatestTouchY = touchY;
+ }
+
+ void setTriggerBack(boolean triggerBack) {
+ if (mTriggerBack != triggerBack && !triggerBack) {
+ mCancelled = true;
+ }
+ mTriggerBack = triggerBack;
+ }
+
+ void setGestureStartLocation(float touchX, float touchY, int swipeEdge) {
+ mInitTouchX = touchX;
+ mInitTouchY = touchY;
+ mSwipeEdge = swipeEdge;
+ mStartThresholdX = mInitTouchX;
+ }
+
+ void reset() {
+ mInitTouchX = 0;
+ mInitTouchY = 0;
+ mStartThresholdX = 0;
+ mCancelled = false;
+ mTriggerBack = false;
+ mSwipeEdge = BackEvent.EDGE_LEFT;
+ }
+
+ BackEvent createStartEvent(RemoteAnimationTarget target) {
+ return new BackEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+ }
+
+ BackEvent createProgressEvent() {
+ float progressThreshold = PROGRESS_THRESHOLD >= 0
+ ? PROGRESS_THRESHOLD : mProgressThreshold;
+ progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
+ float progress = 0;
+ // Progress is always 0 when back is cancelled and not restarted.
+ if (!mCancelled) {
+ // If back is committed, progress is the distance between the last and first touch
+ // point, divided by the max drag distance. Otherwise, it's the distance between
+ // the last touch point and the starting threshold, divided by max drag distance.
+ // The starting threshold is initially the first touch location, and updated to
+ // the location everytime back is restarted after being cancelled.
+ float startX = mTriggerBack ? mInitTouchX : mStartThresholdX;
+ float deltaX = Math.max(
+ mSwipeEdge == BackEvent.EDGE_LEFT
+ ? mLatestTouchX - startX
+ : startX - mLatestTouchX,
+ 0);
+ progress = Math.min(Math.max(deltaX / progressThreshold, 0), 1);
+ }
+ return createProgressEvent(progress);
+ }
+
+ BackEvent createProgressEvent(float progress) {
+ return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+ }
+
+ public void setProgressThreshold(float progressThreshold) {
+ mProgressThreshold = progressThreshold;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
index d6803e8052c6..d3a9a672ec76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
@@ -52,7 +52,7 @@ public class BubbleBadgeIconFactory extends BaseIconFactory {
userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon);
}
Bitmap userBadgedBitmap = createIconBitmap(
- userBadgedAppIcon, 1, BITMAP_GENERATION_MODE_WITH_SHADOW);
+ userBadgedAppIcon, 1, MODE_WITH_SHADOW);
return createIconBitmap(userBadgedBitmap);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 93413dbe7e5f..725b20525bf7 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
@@ -28,10 +28,6 @@ import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTRO
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;
-import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_LEFT;
-import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_NONE;
-import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_RIGHT;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_GROUP_CANCELLED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_INVALID_INTENT;
@@ -41,6 +37,7 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
+import static com.android.wm.shell.floating.FloatingTasksController.SHOW_FLOATING_TASKS_AS_BUBBLES;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
@@ -59,10 +56,8 @@ import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -126,18 +121,6 @@ public class BubbleController implements ConfigurationChangeListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
- // TODO(b/173386799) keep in sync with Launcher3, not hooked up to anything
- public static final String EXTRA_TASKBAR_CREATED = "taskbarCreated";
- public static final String EXTRA_BUBBLE_OVERFLOW_OPENED = "bubbleOverflowOpened";
- public static final String EXTRA_TASKBAR_VISIBLE = "taskbarVisible";
- public static final String EXTRA_TASKBAR_POSITION = "taskbarPosition";
- public static final String EXTRA_TASKBAR_ICON_SIZE = "taskbarIconSize";
- public static final String EXTRA_TASKBAR_BUBBLE_XY = "taskbarBubbleXY";
- public static final String EXTRA_TASKBAR_SIZE = "taskbarSize";
- public static final String LEFT_POSITION = "Left";
- public static final String RIGHT_POSITION = "Right";
- public static final String BOTTOM_POSITION = "Bottom";
-
// Should match with PhoneWindowManager
private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
@@ -470,52 +453,6 @@ public class BubbleController implements ConfigurationChangeListener {
mBubbleData.setExpanded(true);
}
- /** Called when any taskbar state changes (e.g. visibility, position, sizes). */
- private void onTaskbarChanged(Bundle b) {
- if (b == null) {
- return;
- }
- boolean isVisible = b.getBoolean(EXTRA_TASKBAR_VISIBLE, false /* default */);
- String position = b.getString(EXTRA_TASKBAR_POSITION, RIGHT_POSITION /* default */);
- @BubblePositioner.TaskbarPosition int taskbarPosition = TASKBAR_POSITION_NONE;
- switch (position) {
- case LEFT_POSITION:
- taskbarPosition = TASKBAR_POSITION_LEFT;
- break;
- case RIGHT_POSITION:
- taskbarPosition = TASKBAR_POSITION_RIGHT;
- break;
- case BOTTOM_POSITION:
- taskbarPosition = TASKBAR_POSITION_BOTTOM;
- break;
- }
- int[] itemPosition = b.getIntArray(EXTRA_TASKBAR_BUBBLE_XY);
- int iconSize = b.getInt(EXTRA_TASKBAR_ICON_SIZE);
- int taskbarSize = b.getInt(EXTRA_TASKBAR_SIZE);
- Log.w(TAG, "onTaskbarChanged:"
- + " isVisible: " + isVisible
- + " position: " + position
- + " itemPosition: " + itemPosition[0] + "," + itemPosition[1]
- + " iconSize: " + iconSize);
- PointF point = new PointF(itemPosition[0], itemPosition[1]);
- mBubblePositioner.setPinnedLocation(isVisible ? point : null);
- mBubblePositioner.updateForTaskbar(iconSize, taskbarPosition, isVisible, taskbarSize);
- if (mStackView != null) {
- if (isVisible && b.getBoolean(EXTRA_TASKBAR_CREATED, false /* default */)) {
- // If taskbar was created, add and remove the window so that bubbles display on top
- removeFromWindowManagerMaybe();
- addToWindowManagerMaybe();
- }
- mStackView.updateStackPosition();
- mBubbleIconFactory = new BubbleIconFactory(mContext);
- mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext);
- mStackView.onDisplaySizeChanged();
- }
- if (b.getBoolean(EXTRA_BUBBLE_OVERFLOW_OPENED, false)) {
- openBubbleOverflow();
- }
- }
-
/**
* Called when the status bar has become visible or invisible (either permanently or
* temporarily).
@@ -654,6 +591,11 @@ public class BubbleController implements ConfigurationChangeListener {
}
mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
+ if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubblePositioner.isLargeScreen()) {
+ mBubblePositioner.setUsePinnedLocation(true);
+ } else {
+ mBubblePositioner.setUsePinnedLocation(false);
+ }
addToWindowManagerMaybe();
}
@@ -1732,13 +1674,6 @@ public class BubbleController implements ConfigurationChangeListener {
}
@Override
- public void onTaskbarChanged(Bundle b) {
- mMainExecutor.execute(() -> {
- BubbleController.this.onTaskbarChanged(b);
- });
- }
-
- @Override
public boolean handleDismissalInterception(BubbleEntry entry,
@Nullable List<BubbleEntry> children, IntConsumer removeCallback,
Executor callbackExecutor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
index 5dab8a071f76..4ded3ea951e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
@@ -79,6 +79,6 @@ public class BubbleIconFactory extends BaseIconFactory {
true /* shrinkNonAdaptiveIcons */,
null /* outscale */,
outScale);
- return createIconBitmap(icon, outScale[0], BITMAP_GENERATION_MODE_WITH_SHADOW);
+ return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index dbad5df9cf56..07c58527a815 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
@@ -713,6 +713,9 @@ public class BubblePositioner {
* is being shown.
*/
public PointF getDefaultStartPosition() {
+ if (mPinLocation != null) {
+ return mPinLocation;
+ }
// Start on the left if we're in LTR, right otherwise.
final boolean startOnLeft =
mContext.getResources().getConfiguration().getLayoutDirection()
@@ -766,11 +769,18 @@ public class BubblePositioner {
}
/**
- * In some situations bubbles will be pinned to a specific onscreen location. This sets the
- * location to anchor the stack to.
+ * In some situations bubbles will be pinned to a specific onscreen location. This sets whether
+ * bubbles should be pinned or not.
*/
- public void setPinnedLocation(PointF point) {
- mPinLocation = point;
+ public void setUsePinnedLocation(boolean usePinnedLocation) {
+ if (usePinnedLocation) {
+ mShowingInTaskbar = true;
+ mPinLocation = new PointF(mPositionRect.right - mBubbleSize,
+ mPositionRect.bottom - mBubbleSize);
+ } else {
+ mPinLocation = null;
+ mShowingInTaskbar = false;
+ }
}
/**
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 be100bb1dd34..6efad097e3cc 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
@@ -613,16 +613,11 @@ public class BubbleStackView extends FrameLayout
mBubbleContainer.setActiveController(mStackAnimationController);
hideFlyoutImmediate();
- if (mPositioner.showingInTaskbar()) {
- // In taskbar, the stack isn't draggable so we shouldn't dispatch touch events.
- mMagnetizedObject = null;
- } else {
- // Save the magnetized stack so we can dispatch touch events to it.
- mMagnetizedObject = mStackAnimationController.getMagnetizedStack();
- mMagnetizedObject.clearAllTargets();
- mMagnetizedObject.addTarget(mMagneticTarget);
- mMagnetizedObject.setMagnetListener(mStackMagnetListener);
- }
+ // Save the magnetized stack so we can dispatch touch events to it.
+ mMagnetizedObject = mStackAnimationController.getMagnetizedStack();
+ mMagnetizedObject.clearAllTargets();
+ mMagnetizedObject.addTarget(mMagneticTarget);
+ mMagnetizedObject.setMagnetListener(mStackMagnetListener);
mIsDraggingStack = true;
@@ -641,10 +636,7 @@ public class BubbleStackView extends FrameLayout
public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
float viewInitialY, float dx, float dy) {
// If we're expanding or collapsing, ignore all touch events.
- if (mIsExpansionAnimating
- // Also ignore events if we shouldn't be draggable.
- || (mPositioner.showingInTaskbar() && !mIsExpanded)
- || mShowedUserEducationInTouchListenerActive) {
+ if (mIsExpansionAnimating || mShowedUserEducationInTouchListenerActive) {
return;
}
@@ -661,7 +653,7 @@ public class BubbleStackView extends FrameLayout
// bubble since it's stuck to the target.
if (!passEventToMagnetizedObject(ev)) {
updateBubbleShadows(true /* showForAllBubbles */);
- if (mBubbleData.isExpanded() || mPositioner.showingInTaskbar()) {
+ if (mBubbleData.isExpanded()) {
mExpandedAnimationController.dragBubbleOut(
v, viewInitialX + dx, viewInitialY + dy);
} else {
@@ -678,9 +670,7 @@ public class BubbleStackView extends FrameLayout
public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
float viewInitialY, float dx, float dy, float velX, float velY) {
// If we're expanding or collapsing, ignore all touch events.
- if (mIsExpansionAnimating
- // Also ignore events if we shouldn't be draggable.
- || (mPositioner.showingInTaskbar() && !mIsExpanded)) {
+ if (mIsExpansionAnimating) {
return;
}
if (mShowedUserEducationInTouchListenerActive) {
@@ -696,6 +686,8 @@ public class BubbleStackView extends FrameLayout
// Re-show the expanded view if we hid it.
showExpandedViewIfNeeded();
+ } else if (mPositioner.showingInTaskbar()) {
+ mStackAnimationController.snapStackBack();
} else {
// Fling the stack to the edge, and save whether or not it's going to end up on
// the left side of the screen.
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 b3104b518440..7f891ec6d215 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
@@ -23,7 +23,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.app.NotificationChannel;
import android.content.pm.UserInfo;
-import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
@@ -114,9 +113,6 @@ public interface Bubbles {
@Nullable
Bubble getBubbleWithShortcutId(String shortcutId);
- /** Called for any taskbar changes. */
- void onTaskbarChanged(Bundle b);
-
/**
* We intercept notification entries (including group summaries) dismissed by the user when
* there is an active bubble associated with it. We do this so that developers can still
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 961722ba9bc0..0ee0ea60a1bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -417,6 +417,17 @@ public class StackAnimationController extends
}
/**
+ * Snaps the stack back to the previous resting position.
+ */
+ public void snapStackBack() {
+ if (mLayout == null) {
+ return;
+ }
+ PointF p = getStackPositionAlongNearestHorizontalEdge();
+ springStackAfterFling(p.x, p.y);
+ }
+
+ /**
* Where the stack would be if it were snapped to the nearest horizontal edge (left or right).
*/
public PointF getStackPositionAlongNearestHorizontalEdge() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 44a467ffcf3d..cbd544cc4b86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -18,9 +18,21 @@ package com.android.wm.shell.desktopmode;
import com.android.wm.shell.common.annotations.ExternalThread;
+import java.util.concurrent.Executor;
+
/**
* Interface to interact with desktop mode feature in shell.
*/
@ExternalThread
public interface DesktopMode {
+
+ /**
+ * Adds a listener to find out about changes in the visibility of freeform tasks.
+ *
+ * @param listener the listener to add.
+ * @param callbackExecutor the executor to call the listener on.
+ */
+ void addListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+ Executor callbackExecutor);
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index b96facf4c46e..34ff6d814c8d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
@@ -60,6 +61,7 @@ import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.concurrent.Executor;
/**
* Handles windowing changes when desktop mode system setting changes
@@ -132,6 +134,17 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
return new IDesktopModeImpl(this);
}
+ /**
+ * Adds a listener to find out about changes in the visibility of freeform tasks.
+ *
+ * @param listener the listener to add.
+ * @param callbackExecutor the executor to call the listener on.
+ */
+ public void addListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+ Executor callbackExecutor) {
+ mDesktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor);
+ }
+
@VisibleForTesting
void updateDesktopModeActive(boolean active) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
@@ -181,7 +194,18 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
/**
* Show apps on desktop
*/
- WindowContainerTransaction showDesktopApps() {
+ void showDesktopApps() {
+ WindowContainerTransaction wct = bringDesktopAppsToFront();
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */);
+ } else {
+ mShellTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ @NonNull
+ private WindowContainerTransaction bringDesktopAppsToFront() {
ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
@@ -197,11 +221,6 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
for (RunningTaskInfo task : taskInfos) {
wct.reorder(task.token, true);
}
-
- if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
- mShellTaskOrganizer.applyTransaction(wct);
- }
-
return wct;
}
@@ -237,17 +256,29 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
-
- // Only do anything if we are in desktop mode and opening a task/app
- if (!DesktopModeStatus.isActive(mContext) || request.getType() != TRANSIT_OPEN) {
+ // Only do anything if we are in desktop mode and opening a task/app in freeform
+ if (!DesktopModeStatus.isActive(mContext)) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "skip shell transition request: desktop mode not active");
+ return null;
+ }
+ if (request.getType() != TRANSIT_OPEN) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "skip shell transition request: only supports TRANSIT_OPEN");
return null;
}
+ if (request.getTriggerTask() == null
+ || request.getTriggerTask().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "skip shell transition request: not freeform task");
+ return null;
+ }
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request);
WindowContainerTransaction wct = mTransitions.dispatchRequest(transition, request, this);
if (wct == null) {
wct = new WindowContainerTransaction();
}
- wct.merge(showDesktopApps(), true /* transfer */);
+ wct.merge(bringDesktopAppsToFront(), true /* transfer */);
wct.reorder(request.getTriggerTask().token, true /* onTop */);
return wct;
@@ -293,7 +324,14 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
*/
@ExternalThread
private final class DesktopModeImpl implements DesktopMode {
- // Do nothing
+
+ @Override
+ public void addListener(DesktopModeTaskRepository.VisibleTasksListener listener,
+ Executor callbackExecutor) {
+ mMainExecutor.execute(() -> {
+ DesktopModeController.this.addListener(listener, callbackExecutor);
+ });
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 988601c0e8a8..c91d54a62ae6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -16,7 +16,9 @@
package com.android.wm.shell.desktopmode
+import android.util.ArrayMap
import android.util.ArraySet
+import java.util.concurrent.Executor
/**
* Keeps track of task data related to desktop mode.
@@ -30,20 +32,39 @@ class DesktopModeTaskRepository {
* Task gets removed from this list when it vanishes. Or when desktop mode is turned off.
*/
private val activeTasks = ArraySet<Int>()
- private val listeners = ArraySet<Listener>()
+ private val visibleTasks = ArraySet<Int>()
+ private val activeTasksListeners = ArraySet<ActiveTasksListener>()
+ // Track visible tasks separately because a task may be part of the desktop but not visible.
+ private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
/**
- * Add a [Listener] to be notified of updates to the repository.
+ * Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository.
*/
- fun addListener(listener: Listener) {
- listeners.add(listener)
+ fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) {
+ activeTasksListeners.add(activeTasksListener)
}
/**
- * Remove a previously registered [Listener]
+ * Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not.
*/
- fun removeListener(listener: Listener) {
- listeners.remove(listener)
+ fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
+ visibleTasksListeners.put(visibleTasksListener, executor)
+ executor.execute(
+ Runnable { visibleTasksListener.onVisibilityChanged(visibleTasks.size > 0) })
+ }
+
+ /**
+ * Remove a previously registered [ActiveTasksListener]
+ */
+ fun removeActiveTasksListener(activeTasksListener: ActiveTasksListener) {
+ activeTasksListeners.remove(activeTasksListener)
+ }
+
+ /**
+ * Remove a previously registered [VisibleTasksListener]
+ */
+ fun removeVisibleTasksListener(visibleTasksListener: VisibleTasksListener) {
+ visibleTasksListeners.remove(visibleTasksListener)
}
/**
@@ -52,7 +73,7 @@ class DesktopModeTaskRepository {
fun addActiveTask(taskId: Int) {
val added = activeTasks.add(taskId)
if (added) {
- listeners.onEach { it.onActiveTasksChanged() }
+ activeTasksListeners.onEach { it.onActiveTasksChanged() }
}
}
@@ -62,7 +83,7 @@ class DesktopModeTaskRepository {
fun removeActiveTask(taskId: Int) {
val removed = activeTasks.remove(taskId)
if (removed) {
- listeners.onEach { it.onActiveTasksChanged() }
+ activeTasksListeners.onEach { it.onActiveTasksChanged() }
}
}
@@ -81,9 +102,43 @@ class DesktopModeTaskRepository {
}
/**
- * Defines interface for classes that can listen to changes in repository state.
+ * Updates whether a freeform task with this id is visible or not and notifies listeners.
+ */
+ fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) {
+ val prevCount: Int = visibleTasks.size
+ if (visible) {
+ visibleTasks.add(taskId)
+ } else {
+ visibleTasks.remove(taskId)
+ }
+ if (prevCount == 0 && visibleTasks.size == 1 ||
+ prevCount > 0 && visibleTasks.size == 0) {
+ for ((listener, executor) in visibleTasksListeners) {
+ executor.execute(
+ Runnable { listener.onVisibilityChanged(visibleTasks.size > 0) })
+ }
+ }
+ }
+
+ /**
+ * Defines interface for classes that can listen to changes for active tasks in desktop mode.
+ */
+ interface ActiveTasksListener {
+ /**
+ * Called when the active tasks change in desktop mode.
+ */
+ @JvmDefault
+ fun onActiveTasksChanged() {}
+ }
+
+ /**
+ * Defines interface for classes that can listen to changes for visible tasks in desktop mode.
*/
- interface Listener {
- fun onActiveTasksChanged()
+ interface VisibleTasksListener {
+ /**
+ * Called when the desktop starts or stops showing freeform tasks.
+ */
+ @JvmDefault
+ fun onVisibilityChanged(hasVisibleFreeformTasks: Boolean) {}
}
}
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 f82a34621262..eaa7158abbe5 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
@@ -90,6 +90,8 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"Adding active freeform task: #%d", taskInfo.taskId);
mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
+ mDesktopModeTaskRepository.ifPresent(
+ it -> it.updateVisibleFreeformTasks(taskInfo.taskId, true));
}
}
@@ -103,6 +105,8 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"Removing active freeform task: #%d", taskInfo.taskId);
mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
+ mDesktopModeTaskRepository.ifPresent(
+ it -> it.updateVisibleFreeformTasks(taskInfo.taskId, false));
}
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -124,6 +128,8 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
"Adding active freeform task: #%d", taskInfo.taskId);
mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
}
+ mDesktopModeTaskRepository.ifPresent(
+ it -> it.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index afb64c9eec41..43d3f36f1fe5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -60,7 +60,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
FloatingContentCoordinator.FloatingContent {
public static final boolean ENABLE_FLING_TO_DISMISS_PIP =
- SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_pip", true);
+ SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_pip", false);
private static final String TAG = "PipMotionHelper";
private static final boolean DEBUG = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index b71cc32a0347..1a6c1d65db03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -16,7 +16,7 @@
package com.android.wm.shell.recents;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -44,5 +44,5 @@ interface IRecentTasks {
/**
* Gets the set of running tasks.
*/
- ActivityManager.RunningTaskInfo[] getRunningTasks(int maxNum) = 4;
+ RunningTaskInfo[] getRunningTasks(int maxNum) = 4;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index 59f72335678e..e8f58fe2bfad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -16,7 +16,7 @@
package com.android.wm.shell.recents;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
/**
* Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
@@ -31,10 +31,10 @@ oneway interface IRecentTasksListener {
/**
* Called when a running task appears.
*/
- void onRunningTaskAppeared(in ActivityManager.RunningTaskInfo taskInfo);
+ void onRunningTaskAppeared(in RunningTaskInfo taskInfo);
/**
* Called when a running task vanishes.
*/
- void onRunningTaskVanished(in ActivityManager.RunningTaskInfo taskInfo);
-} \ No newline at end of file
+ void onRunningTaskVanished(in RunningTaskInfo taskInfo);
+}
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 08f3db65e62f..f9172ba183de 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
@@ -68,7 +68,7 @@ import java.util.function.Consumer;
* Manages the recent task list from the system, caching it as necessary.
*/
public class RecentTasksController implements TaskStackListenerCallback,
- RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.Listener {
+ RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.ActiveTasksListener {
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
@@ -147,7 +147,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
- mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this));
+ mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this));
}
/**
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 9102bd3a9b6a..e2ac01f7b003 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
@@ -207,6 +207,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
private DefaultMixedHandler mMixedHandler;
+ private final Toast mSplitUnsupportedToast;
private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
new SplitWindowManager.ParentContainerCallbacks() {
@@ -300,6 +301,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
transitions.addHandler(this);
mTaskOrganizer.addFocusListener(this);
+ mSplitUnsupportedToast = Toast.makeText(mContext,
+ R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
}
@VisibleForTesting
@@ -329,6 +332,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayController.addDisplayWindowListener(this);
mDisplayLayout = new DisplayLayout();
transitions.addHandler(this);
+ mSplitUnsupportedToast = Toast.makeText(mContext,
+ R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
}
public void setMixedHandler(DefaultMixedHandler mixedHandler) {
@@ -470,6 +475,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainExecutor.execute(() ->
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ mSplitUnsupportedToast.show();
} else {
// Switch the split position if launching as MULTIPLE_TASK failed.
if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
@@ -736,6 +742,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainExecutor.execute(() ->
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ mSplitUnsupportedToast.show();
} else {
mSyncQueue.queue(evictWct);
}
@@ -2287,13 +2294,11 @@ public 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();
+ mSplitUnsupportedToast.show();
return;
}
@@ -2302,7 +2307,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
prepareExitSplitScreen(stageType, wct);
mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
- splitUnsupportedToast.show();
+ mSplitUnsupportedToast.show();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 8cee4f1dc8fb..6ce981e25f5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -432,7 +432,8 @@ public class SplashscreenContentDrawer {
final ShapeIconFactory factory = new ShapeIconFactory(
SplashscreenContentDrawer.this.mContext,
scaledIconDpi, mFinalIconSize);
- final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(iconDrawable);
+ final Bitmap bitmap = factory.createScaledBitmap(iconDrawable,
+ BaseIconFactory.MODE_DEFAULT);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
createIconDrawable(new BitmapDrawable(bitmap), true,
mHighResIconProvider.mLoadInDetail);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
index e90389764af3..f209521b1da4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
@@ -33,6 +33,8 @@ public class SplitBounds implements Parcelable {
// This class is orientation-agnostic, so we compute both for later use
public final float topTaskPercent;
public final float leftTaskPercent;
+ public final float dividerWidthPercent;
+ public final float dividerHeightPercent;
/**
* If {@code true}, that means at the time of creation of this object, the
* split-screened apps were vertically stacked. This is useful in scenarios like
@@ -62,8 +64,12 @@ public class SplitBounds implements Parcelable {
appsStackedVertically = false;
}
- leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
- topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
+ float totalWidth = rightBottomBounds.right - leftTopBounds.left;
+ float totalHeight = rightBottomBounds.bottom - leftTopBounds.top;
+ leftTaskPercent = leftTopBounds.width() / totalWidth;
+ topTaskPercent = leftTopBounds.height() / totalHeight;
+ dividerWidthPercent = visualDividerBounds.width() / totalWidth;
+ dividerHeightPercent = visualDividerBounds.height() / totalHeight;
}
public SplitBounds(Parcel parcel) {
@@ -75,6 +81,8 @@ public class SplitBounds implements Parcelable {
appsStackedVertically = parcel.readBoolean();
leftTopTaskId = parcel.readInt();
rightBottomTaskId = parcel.readInt();
+ dividerWidthPercent = parcel.readInt();
+ dividerHeightPercent = parcel.readInt();
}
@Override
@@ -87,6 +95,8 @@ public class SplitBounds implements Parcelable {
parcel.writeBoolean(appsStackedVertically);
parcel.writeInt(leftTopTaskId);
parcel.writeInt(rightBottomTaskId);
+ parcel.writeFloat(dividerWidthPercent);
+ parcel.writeFloat(dividerHeightPercent);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index beace75d9fca..9d61c14e1435 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -21,7 +21,6 @@ import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
-import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.view.Choreographer;
@@ -43,22 +42,6 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus;
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
*/
public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
- // The thickness of shadows of a window that has focus in DIP.
- private static final int DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP = 20;
- // The thickness of shadows of a window that doesn't have focus in DIP.
- private static final int DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP = 5;
-
- // Height of button (32dp) + 2 * margin (5dp each)
- private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42;
- // Width of buttons (64dp) + handle (128dp) + padding (24dp total)
- private static final int DECOR_CAPTION_WIDTH_IN_DIP = 216;
- private static final int RESIZE_HANDLE_IN_DIP = 30;
- private static final int RESIZE_CORNER_IN_DIP = 44;
-
- private static final Rect EMPTY_OUTSET = new Rect();
- private static final Rect RESIZE_HANDLE_OUTSET = new Rect(
- RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP);
-
private final Handler mHandler;
private final Choreographer mChoreographer;
private final SyncTransactionQueue mSyncQueue;
@@ -69,6 +52,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
private DragResizeInputListener mDragResizeListener;
+ private RelayoutParams mRelayoutParams = new RelayoutParams();
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
@@ -114,19 +98,32 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
- final int shadowRadiusDp = taskInfo.isFocused
- ? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP;
- final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode()
- == WindowConfiguration.WINDOWING_MODE_FREEFORM;
- final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable;
- final Rect outset = isDragResizeable ? RESIZE_HANDLE_OUTSET : EMPTY_OUTSET;
+ final int shadowRadiusID = taskInfo.isFocused
+ ? R.dimen.freeform_decor_shadow_focused_thickness
+ : R.dimen.freeform_decor_shadow_unfocused_thickness;
+ final boolean isFreeform =
+ taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
+ final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- relayout(taskInfo, R.layout.caption_window_decoration, oldRootView,
- DECOR_CAPTION_HEIGHT_IN_DIP, DECOR_CAPTION_WIDTH_IN_DIP, outset, shadowRadiusDp,
- startT, finishT, wct, mResult);
+
+ int outsetLeftId = R.dimen.freeform_resize_handle;
+ int outsetTopId = R.dimen.freeform_resize_handle;
+ int outsetRightId = R.dimen.freeform_resize_handle;
+ int outsetBottomId = R.dimen.freeform_resize_handle;
+
+ mRelayoutParams.reset();
+ mRelayoutParams.mRunningTaskInfo = taskInfo;
+ mRelayoutParams.mLayoutResId = R.layout.caption_window_decoration;
+ mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
+ mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width;
+ mRelayoutParams.mShadowRadiusId = shadowRadiusID;
+ if (isDragResizeable) {
+ mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
+ }
+ relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
mTaskOrganizer.applyTransaction(wct);
@@ -167,10 +164,12 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
}
int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
-
+ int resize_handle = mResult.mRootView.getResources()
+ .getDimensionPixelSize(R.dimen.freeform_resize_handle);
+ int resize_corner = mResult.mRootView.getResources()
+ .getDimensionPixelSize(R.dimen.freeform_resize_corner);
mDragResizeListener.setGeometry(
- mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP),
- (int) (mResult.mDensity * RESIZE_CORNER_IN_DIP), touchSlop);
+ mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
}
/**
@@ -218,7 +217,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
View handle = caption.findViewById(R.id.caption_handle);
VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
handleBackground.setTintList(buttonTintColor);
- caption.setBackgroundColor(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
+ caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
}
private void closeDragResizeListener() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index bf863ea2c7ab..b314163802ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -19,11 +19,11 @@ package com.android.wm.shell.windowdecor;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.util.DisplayMetrics;
import android.view.Display;
import android.view.InsetsState;
import android.view.LayoutInflater;
@@ -91,7 +91,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
SurfaceControl mTaskBackgroundSurface;
SurfaceControl mCaptionContainerSurface;
- private CaptionWindowManager mCaptionWindowManager;
+ private WindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
private final Rect mCaptionInsetsRect = new Rect();
@@ -142,15 +142,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
*/
abstract void relayout(RunningTaskInfo taskInfo);
- void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp,
- float captionWidthDp, Rect outsetsDp, float shadowRadiusDp,
- SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- WindowContainerTransaction wct, RelayoutResult<T> outResult) {
+ void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
+ RelayoutResult<T> outResult) {
outResult.reset();
final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
- if (taskInfo != null) {
- mTaskInfo = taskInfo;
+ if (params.mRunningTaskInfo != null) {
+ mTaskInfo = params.mRunningTaskInfo;
}
if (!mTaskInfo.isVisible) {
@@ -159,7 +158,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return;
}
- if (rootView == null && layoutResId == 0) {
+ if (rootView == null && params.mLayoutResId == 0) {
throw new IllegalArgumentException("layoutResId and rootView can't both be invalid.");
}
@@ -176,15 +175,15 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return;
}
mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
- if (layoutResId != 0) {
- outResult.mRootView =
- (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
+ if (params.mLayoutResId != 0) {
+ outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
+ .inflate(params.mLayoutResId, null);
}
}
if (outResult.mRootView == null) {
- outResult.mRootView =
- (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
+ outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
+ .inflate(params.mLayoutResId , null);
}
// DecorationContainerSurface
@@ -200,18 +199,19 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
- outResult.mDensity = taskConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
- final int decorContainerOffsetX = -(int) (outsetsDp.left * outResult.mDensity);
- final int decorContainerOffsetY = -(int) (outsetsDp.top * outResult.mDensity);
+ final Resources resources = mDecorWindowContext.getResources();
+ final int decorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
+ final int decorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
outResult.mWidth = taskBounds.width()
- + (int) (outsetsDp.right * outResult.mDensity)
+ + loadDimensionPixelSize(resources, params.mOutsetRightId)
- decorContainerOffsetX;
outResult.mHeight = taskBounds.height()
- + (int) (outsetsDp.bottom * outResult.mDensity)
+ + loadDimensionPixelSize(resources, params.mOutsetBottomId)
- decorContainerOffsetY;
startT.setPosition(
mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
- .setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
+ .setWindowCrop(mDecorationContainerSurface,
+ outResult.mWidth, outResult.mHeight)
// TODO(b/244455401): Change the z-order when it's better organized
.setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1)
.show(mDecorationContainerSurface);
@@ -226,12 +226,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.build();
}
- float shadowRadius = outResult.mDensity * shadowRadiusDp;
+ float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
- startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
+ startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(),
+ taskBounds.height())
.setShadowRadius(mTaskBackgroundSurface, shadowRadius)
.setColor(mTaskBackgroundSurface, mTmpColor)
// TODO(b/244455401): Change the z-order when it's better organized
@@ -248,8 +249,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.build();
}
- final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity);
- final int captionWidth = (int) Math.ceil(captionWidthDp * outResult.mDensity);
+ final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
+ final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId);
//Prevent caption from going offscreen if task is too high up
final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2;
@@ -264,8 +265,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
if (mCaptionWindowManager == null) {
// Put caption under a container surface because ViewRootImpl sets the destination frame
// of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
- mCaptionWindowManager = new CaptionWindowManager(
- mTaskInfo.getConfiguration(), mCaptionContainerSurface);
+ mCaptionWindowManager = new WindowlessWindowManager(
+ mTaskInfo.getConfiguration(), mCaptionContainerSurface,
+ null /* hostInputToken */);
}
// Caption view
@@ -289,8 +291,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Caption insets
mCaptionInsetsRect.set(taskBounds);
- mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight - captionYPos;
- wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES);
+ mCaptionInsetsRect.bottom =
+ mCaptionInsetsRect.top + captionHeight - captionYPos;
+ wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect,
+ CAPTION_INSETS_TYPES);
} else {
startT.hide(mCaptionContainerSurface);
}
@@ -365,34 +369,67 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
releaseViews();
}
+ private static int loadDimensionPixelSize(Resources resources, int resourceId) {
+ if (resourceId == Resources.ID_NULL) {
+ return 0;
+ }
+ return resources.getDimensionPixelSize(resourceId);
+ }
+
+ private static float loadDimension(Resources resources, int resourceId) {
+ if (resourceId == Resources.ID_NULL) {
+ return 0;
+ }
+ return resources.getDimension(resourceId);
+ }
+
+ static class RelayoutParams{
+ RunningTaskInfo mRunningTaskInfo;
+ int mLayoutResId;
+ int mCaptionHeightId;
+ int mCaptionWidthId;
+ int mShadowRadiusId;
+
+ int mOutsetTopId;
+ int mOutsetBottomId;
+ int mOutsetLeftId;
+ int mOutsetRightId;
+
+ void setOutsets(int leftId, int topId, int rightId, int bottomId) {
+ mOutsetLeftId = leftId;
+ mOutsetTopId = topId;
+ mOutsetRightId = rightId;
+ mOutsetBottomId = bottomId;
+ }
+
+ void reset() {
+ mLayoutResId = Resources.ID_NULL;
+ mCaptionHeightId = Resources.ID_NULL;
+ mCaptionWidthId = Resources.ID_NULL;
+ mShadowRadiusId = Resources.ID_NULL;
+
+ mOutsetTopId = Resources.ID_NULL;
+ mOutsetBottomId = Resources.ID_NULL;
+ mOutsetLeftId = Resources.ID_NULL;
+ mOutsetRightId = Resources.ID_NULL;
+ }
+ }
+
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
int mWidth;
int mHeight;
- float mDensity;
T mRootView;
void reset() {
mWidth = 0;
mHeight = 0;
- mDensity = 0;
mRootView = null;
}
}
- private static class CaptionWindowManager extends WindowlessWindowManager {
- CaptionWindowManager(Configuration config, SurfaceControl rootSurface) {
- super(config, rootSurface, null /* hostInputToken */);
- }
-
- @Override
- public void setConfiguration(Configuration configuration) {
- super.setConfiguration(configuration);
- }
- }
-
interface SurfaceControlViewHostFactory {
default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
return new SurfaceControlViewHost(c, d, wmm);
}
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
new file mode 100644
index 000000000000..8949a75d1a15
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <!-- Resources used in WindowDecorationTests -->
+ <dimen name="test_freeform_decor_caption_height">32dp</dimen>
+ <dimen name="test_freeform_decor_caption_width">216dp</dimen>
+ <dimen name="test_window_decor_left_outset">10dp</dimen>
+ <dimen name="test_window_decor_top_outset">20dp</dimen>
+ <dimen name="test_window_decor_right_outset">30dp</dimen>
+ <dimen name="test_window_decor_bottom_outset">40dp</dimen>
+ <dimen name="test_window_decor_shadow_radius">5dp</dimen>
+ <dimen name="test_window_decor_resize_handle">10dp</dimen>
+</resources> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 077e9ca2e88c..2e328b0736dd 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
@@ -127,6 +127,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mActivityTaskManager, mContext,
mContentResolver);
+ mController.setEnableUAnimation(true);
mShellInit.init();
mEventTime = 0;
mShellExecutor.flushAll();
@@ -245,10 +246,10 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted();
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
- verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
+ verify(mIOnBackInvokedCallback).onBackStarted(backEventCaptor.capture());
assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
+ verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
@@ -276,11 +277,11 @@ public class BackAnimationControllerTest extends ShellTestCase {
triggerBackGesture();
- verify(appCallback, never()).onBackStarted();
+ verify(appCallback, never()).onBackStarted(any(BackEvent.class));
verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(appCallback, times(1)).onBackInvoked();
- verify(mIOnBackInvokedCallback, never()).onBackStarted();
+ verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class));
verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(mIOnBackInvokedCallback, never()).onBackInvoked();
}
@@ -313,7 +314,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted();
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
}
@Test
@@ -332,7 +333,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted();
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
}
@@ -348,7 +349,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
- verify(mIOnBackInvokedCallback).onBackStarted();
+ verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
new file mode 100644
index 000000000000..3aefc3f03a8a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static org.junit.Assert.assertEquals;
+
+import android.window.BackEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class TouchTrackerTest {
+ private static final float FAKE_THRESHOLD = 400;
+ private static final float INITIAL_X_LEFT_EDGE = 5;
+ private static final float INITIAL_X_RIGHT_EDGE = FAKE_THRESHOLD - INITIAL_X_LEFT_EDGE;
+ private TouchTracker mTouchTracker;
+
+ @Before
+ public void setUp() throws Exception {
+ mTouchTracker = new TouchTracker();
+ mTouchTracker.setProgressThreshold(FAKE_THRESHOLD);
+ }
+
+ @Test
+ public void generatesProgress_onStart() {
+ mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
+ BackEvent event = mTouchTracker.createStartEvent(null);
+ assertEquals(event.getProgress(), 0f, 0f);
+ }
+
+ @Test
+ public void generatesProgress_leftEdge() {
+ mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
+ float touchX = 10;
+
+ // Pre-commit
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
+
+ // Post-commit
+ touchX += 100;
+ mTouchTracker.setTriggerBack(true);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
+
+ // Cancel
+ touchX -= 10;
+ mTouchTracker.setTriggerBack(false);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Cancel more
+ touchX -= 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Restart
+ touchX += 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Restarted, but pre-commit
+ float restartX = touchX;
+ touchX += 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
+
+ // Restarted, post-commit
+ touchX += 10;
+ mTouchTracker.setTriggerBack(true);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
+ }
+
+ @Test
+ public void generatesProgress_rightEdge() {
+ mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
+ float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
+
+ // Pre-commit
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
+
+ // Post-commit
+ touchX -= 100;
+ mTouchTracker.setTriggerBack(true);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
+
+ // Cancel
+ touchX += 10;
+ mTouchTracker.setTriggerBack(false);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Cancel more
+ touchX += 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Restart
+ touchX -= 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), 0, 0f);
+
+ // Restarted, but pre-commit
+ float restartX = touchX;
+ touchX -= 10;
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
+
+ // Restarted, post-commit
+ touchX -= 10;
+ mTouchTracker.setTriggerBack(true);
+ mTouchTracker.update(touchX, 0);
+ assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
+ }
+
+ private float getProgress() {
+ return mTouchTracker.createProgressEvent().getProgress();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index c850a3b3b780..79b520c734c8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -20,6 +20,8 @@ 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 static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -35,10 +37,12 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.testing.AndroidTestingRunner;
import android.window.DisplayAreaInfo;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransaction.Change;
@@ -243,6 +247,44 @@ public class DesktopModeControllerTest extends ShellTestCase {
assertThat(op2.getContainer()).isEqualTo(token2.binder());
}
+ @Test
+ public void testHandleTransitionRequest_desktopModeNotActive_returnsNull() {
+ when(DesktopModeStatus.isActive(any())).thenReturn(false);
+ WindowContainerTransaction wct = mController.handleRequest(
+ new Binder(),
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ assertThat(wct).isNull();
+ }
+
+ @Test
+ public void testHandleTransitionRequest_notTransitOpen_returnsNull() {
+ WindowContainerTransaction wct = mController.handleRequest(
+ new Binder(),
+ new TransitionRequestInfo(TRANSIT_TO_FRONT, null /* trigger */, null /* remote */));
+ assertThat(wct).isNull();
+ }
+
+ @Test
+ public void testHandleTransitionRequest_notFreeform_returnsNull() {
+ ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+ trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ WindowContainerTransaction wct = mController.handleRequest(
+ new Binder(),
+ new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */));
+ assertThat(wct).isNull();
+ }
+
+ @Test
+ public void testHandleTransitionRequest_returnsWct() {
+ ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+ trigger.token = new MockToken().mToken;
+ trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ WindowContainerTransaction wct = mController.handleRequest(
+ mock(IBinder.class),
+ new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */));
+ assertThat(wct).isNotNull();
+ }
+
private static class MockToken {
private final WindowContainerToken mToken;
private final IBinder mBinder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 9b28d11f6a9d..aaa5c8a35acb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -38,7 +39,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
@Test
fun addActiveTask_listenerNotifiedAndTaskIsActive() {
val listener = TestListener()
- repo.addListener(listener)
+ repo.addActiveTaskListener(listener)
repo.addActiveTask(1)
assertThat(listener.activeTaskChangedCalls).isEqualTo(1)
@@ -48,7 +49,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
@Test
fun addActiveTask_sameTaskDoesNotNotify() {
val listener = TestListener()
- repo.addListener(listener)
+ repo.addActiveTaskListener(listener)
repo.addActiveTask(1)
repo.addActiveTask(1)
@@ -58,7 +59,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
@Test
fun addActiveTask_multipleTasksAddedNotifiesForEach() {
val listener = TestListener()
- repo.addListener(listener)
+ repo.addActiveTaskListener(listener)
repo.addActiveTask(1)
repo.addActiveTask(2)
@@ -68,7 +69,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
@Test
fun removeActiveTask_listenerNotifiedAndTaskNotActive() {
val listener = TestListener()
- repo.addListener(listener)
+ repo.addActiveTaskListener(listener)
repo.addActiveTask(1)
repo.removeActiveTask(1)
@@ -80,7 +81,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
@Test
fun removeActiveTask_removeNotExistingTaskDoesNotNotify() {
val listener = TestListener()
- repo.addListener(listener)
+ repo.addActiveTaskListener(listener)
repo.removeActiveTask(99)
assertThat(listener.activeTaskChangedCalls).isEqualTo(0)
}
@@ -90,10 +91,69 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
assertThat(repo.isActiveTask(99)).isFalse()
}
- class TestListener : DesktopModeTaskRepository.Listener {
+ @Test
+ fun addListener_notifiesVisibleFreeformTask() {
+ repo.updateVisibleFreeformTasks(1, true)
+ val listener = TestVisibilityListener()
+ val executor = TestShellExecutor()
+ repo.addVisibleTasksListener(listener, executor)
+ executor.flushAll()
+
+ assertThat(listener.hasVisibleFreeformTasks).isTrue()
+ assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(1)
+ }
+
+ @Test
+ fun updateVisibleFreeformTasks_addVisibleTasksNotifiesListener() {
+ val listener = TestVisibilityListener()
+ val executor = TestShellExecutor()
+ repo.addVisibleTasksListener(listener, executor)
+ repo.updateVisibleFreeformTasks(1, true)
+ repo.updateVisibleFreeformTasks(2, true)
+ executor.flushAll()
+
+ assertThat(listener.hasVisibleFreeformTasks).isTrue()
+ // Equal to 2 because adding the listener notifies the current state
+ assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2)
+ }
+
+ @Test
+ fun updateVisibleFreeformTasks_removeVisibleTasksNotifiesListener() {
+ val listener = TestVisibilityListener()
+ val executor = TestShellExecutor()
+ repo.addVisibleTasksListener(listener, executor)
+ repo.updateVisibleFreeformTasks(1, true)
+ repo.updateVisibleFreeformTasks(2, true)
+ executor.flushAll()
+
+ assertThat(listener.hasVisibleFreeformTasks).isTrue()
+ repo.updateVisibleFreeformTasks(1, false)
+ executor.flushAll()
+
+ // Equal to 2 because adding the listener notifies the current state
+ assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2)
+
+ repo.updateVisibleFreeformTasks(2, false)
+ executor.flushAll()
+
+ assertThat(listener.hasVisibleFreeformTasks).isFalse()
+ assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3)
+ }
+
+ class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
var activeTaskChangedCalls = 0
override fun onActiveTasksChanged() {
activeTaskChangedCalls++
}
}
+
+ class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener {
+ var hasVisibleFreeformTasks = false
+ var visibleFreeformTaskChangedCalls = 0
+
+ override fun onVisibilityChanged(hasVisibleTasks: Boolean) {
+ hasVisibleFreeformTasks = hasVisibleTasks
+ visibleFreeformTaskChangedCalls++
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index fa62b9c00fc7..4d37e5dbc4dc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -50,11 +50,13 @@ import android.view.WindowManager.LayoutParams;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.tests.R;
import org.junit.Before;
import org.junit.Test;
@@ -76,13 +78,9 @@ import java.util.function.Supplier;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class WindowDecorationTests extends ShellTestCase {
- private static final int CAPTION_HEIGHT_DP = 32;
- private static final int CAPTION_WIDTH_DP = 216;
- private static final int SHADOW_RADIUS_DP = 5;
private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
- private final Rect mOutsetsDp = new Rect();
private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
new WindowDecoration.RelayoutResult<>();
@@ -104,6 +102,7 @@ public class WindowDecorationTests extends ShellTestCase {
private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
private SurfaceControl.Transaction mMockSurfaceControlStartT;
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
+ private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
@Before
public void setUp() {
@@ -147,7 +146,11 @@ public class WindowDecorationTests extends ShellTestCase {
// Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
// 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mOutsetsDp.set(10, 20, 30, 40);
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -197,8 +200,11 @@ public class WindowDecorationTests extends ShellTestCase {
// Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
// 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mOutsetsDp.set(10, 20, 30, 40);
-
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -226,16 +232,17 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
+
verify(mMockSurfaceControlViewHost)
.setView(same(mMockView),
argThat(lp -> lp.height == 64
- && lp.width == 300
+ && lp.width == 432
&& (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
if (ViewRootImpl.CAPTION_ON_SHELL) {
verify(mMockView).setTaskFocusState(true);
verify(mMockWindowContainerTransaction)
.addRectInsetsProvider(taskInfo.token,
- new Rect(100, 300, 400, 364),
+ new Rect(100, 300, 400, 332),
new int[] { InsetsState.ITYPE_CAPTION_BAR });
}
@@ -248,7 +255,6 @@ public class WindowDecorationTests extends ShellTestCase {
assertEquals(380, mRelayoutResult.mWidth);
assertEquals(220, mRelayoutResult.mHeight);
- assertEquals(2, mRelayoutResult.mDensity, 0.f);
}
@Test
@@ -287,7 +293,11 @@ public class WindowDecorationTests extends ShellTestCase {
// Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
// 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mOutsetsDp.set(10, 20, 30, 40);
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -358,7 +368,8 @@ public class WindowDecorationTests extends ShellTestCase {
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
- return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
+ return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
+ mMockDisplayController, mMockShellTaskOrganizer,
taskInfo, testSurface,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
@@ -410,9 +421,13 @@ public class WindowDecorationTests extends ShellTestCase {
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
- relayout(null /* taskInfo */, 0 /* layoutResId */, mMockView, CAPTION_HEIGHT_DP,
- CAPTION_WIDTH_DP, mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlStartT,
- mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mRelayoutResult);
+ mRelayoutParams.mLayoutResId = 0;
+ mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
+ mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
+ mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
+
+ relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
+ mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
}
}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index 2fe7b1668d08..262f5f198c22 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -100,16 +100,12 @@ public final class BluetoothMidiDevice {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
+ Log.d(TAG, "onConnectionStateChange() status: " + status + ", newState: " + newState);
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.d(TAG, "Connected to GATT server.");
Log.d(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
- if (!mBluetoothGatt.requestMtu(MAX_PACKET_SIZE)) {
- Log.e(TAG, "request mtu failed");
- mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
- mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
- }
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(TAG, "Disconnected from GATT server.");
close();
@@ -118,6 +114,7 @@ public final class BluetoothMidiDevice {
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ Log.d(TAG, "onServicesDiscovered() status: " + status);
if (status == BluetoothGatt.GATT_SUCCESS) {
BluetoothGattService service = gatt.getService(MIDI_SERVICE);
if (service != null) {
@@ -137,9 +134,14 @@ public final class BluetoothMidiDevice {
// Specification says to read the characteristic first and then
// switch to receiving notifications
mBluetoothGatt.readCharacteristic(characteristic);
- }
- openBluetoothDevice(mBluetoothDevice);
+ // Request higher MTU size
+ if (!gatt.requestMtu(MAX_PACKET_SIZE)) {
+ Log.e(TAG, "request mtu failed");
+ mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
+ mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
+ }
+ }
}
} else {
Log.e(TAG, "onServicesDiscovered received: " + status);
@@ -235,13 +237,13 @@ public final class BluetoothMidiDevice {
System.arraycopy(buffer, 0, mCachedBuffer, 0, count);
if (DEBUG) {
- logByteArray("Sent ", mCharacteristic.getValue(), 0,
- mCharacteristic.getValue().length);
+ logByteArray("Sent ", mCachedBuffer, 0, mCachedBuffer.length);
}
- if (mBluetoothGatt.writeCharacteristic(mCharacteristic, mCachedBuffer,
- mCharacteristic.getWriteType()) != BluetoothGatt.GATT_SUCCESS) {
- Log.w(TAG, "could not write characteristic to Bluetooth GATT");
+ int result = mBluetoothGatt.writeCharacteristic(mCharacteristic, mCachedBuffer,
+ mCharacteristic.getWriteType());
+ if (result != BluetoothGatt.GATT_SUCCESS) {
+ Log.w(TAG, "could not write characteristic to Bluetooth GATT. result: " + result);
return false;
}
@@ -254,6 +256,10 @@ public final class BluetoothMidiDevice {
mBluetoothDevice = device;
mService = service;
+ // Set a small default packet size in case there is an issue with configuring MTUs.
+ mPacketEncoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
+ mPacketDecoder.setMaxPacketSize(DEFAULT_PACKET_SIZE);
+
mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback);
mContext = context;
diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
index 18b8ca9b4dd6..77dfbeee9bbf 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
@@ -16,7 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_label" msgid="4470785958457506021">"隨附裝置管理員"</string>
+ <string name="app_label" msgid="4470785958457506021">"隨附裝置管理工具"</string>
<string name="confirmation_title" msgid="3785000297483688997">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;"</string>
<string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
<string name="chooser_title" msgid="2262294130493605839">"選擇要讓「<xliff:g id="APP_NAME">%2$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 5c9ab7bf2feb..4e7e36797b7e 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -350,7 +350,7 @@ public class CompanionDeviceDiscoveryService extends Service {
}
return;
}
- if (DEBUG) Log.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
+ Log.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
// First: make change.
mDevicesFound.add(device);
@@ -363,9 +363,9 @@ public class CompanionDeviceDiscoveryService extends Service {
});
}
- private void onDeviceLost(@Nullable DeviceFilterPair<?> device) {
+ private void onDeviceLost(@NonNull DeviceFilterPair<?> device) {
runOnMainThread(() -> {
- if (DEBUG) Log.i(TAG, "onDeviceLost(), device=" + device.toShortString());
+ Log.i(TAG, "onDeviceLost(), device=" + device.toShortString());
// First: make change.
mDevicesFound.remove(device);
diff --git a/packages/PackageInstaller/res/values-as/strings.xml b/packages/PackageInstaller/res/values-as/strings.xml
index dd776a986df6..84053353ebb3 100644
--- a/packages/PackageInstaller/res/values-as/strings.xml
+++ b/packages/PackageInstaller/res/values-as/strings.xml
@@ -28,11 +28,11 @@
<string name="install_confirm_question_update" msgid="3348888852318388584">"আপুনি এই এপ্‌টো আপডে’ট কৰিবলৈ বিচাৰেনে?"</string>
<string name="install_failed" msgid="5777824004474125469">"এপ্ ইনষ্টল কৰা হোৱা নাই।"</string>
<string name="install_failed_blocked" msgid="8512284352994752094">"পেকেজটোৰ ইনষ্টল অৱৰোধ কৰা হৈছে।"</string>
- <string name="install_failed_conflict" msgid="3493184212162521426">"এপটো ইনষ্টল কৰিব পৰা নগ\'ল কাৰণ ইয়াৰ সৈতে আগৰে পৰা থকা এটা পেকেজৰ সংঘাত হৈছে।"</string>
- <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"আপোনাৰ টেবলেটৰ সৈতে খাপ নোখোৱাৰ বাবে এপটো ইনষ্টল কৰা নহ\'ল।"</string>
- <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"আপোনাৰ টিভিত এই এপটো নচলে"</string>
- <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"আপোনাৰ ফ\'নৰ সৈতে খাপ নোখোৱাৰ বাবে এপটো ইনষ্টল কৰা নহ\'ল।"</string>
- <string name="install_failed_invalid_apk" msgid="8581007676422623930">"পেকেজটো মান্য নোহোৱাৰ বাবে এপটো ইনষ্টল কৰা নহ\'ল।"</string>
+ <string name="install_failed_conflict" msgid="3493184212162521426">"এপ্‌টো ইনষ্টল কৰিব পৰা নগ\'ল কাৰণ ইয়াৰ সৈতে আগৰে পৰা থকা এটা পেকেজৰ সংঘাত হৈছে।"</string>
+ <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"আপোনাৰ টেবলেটৰ সৈতে খাপ নোখোৱাৰ বাবে এপ্‌টো ইনষ্টল কৰা নহ\'ল।"</string>
+ <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"আপোনাৰ টিভিত এই এপ্‌টো নচলে"</string>
+ <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"আপোনাৰ ফ\'নৰ সৈতে খাপ নোখোৱাৰ বাবে এপ্‌টো ইনষ্টল কৰা নহ\'ল।"</string>
+ <string name="install_failed_invalid_apk" msgid="8581007676422623930">"পেকেজটো মান্য নোহোৱাৰ বাবে এপ্‌টো ইনষ্টল কৰা নহ\'ল।"</string>
<string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"আপোনাৰ টে\'বলেটত <xliff:g id="APP_NAME">%1$s</xliff:g> ইনষ্টল কৰিব পৰা নগ\'ল৷"</string>
<string name="install_failed_msg" product="tv" msgid="1920009940048975221">"আপোনাৰ টিভিত <xliff:g id="APP_NAME">%1$s</xliff:g> ইনষ্টল কৰিব পৰা নগ\'ল।"</string>
<string name="install_failed_msg" product="default" msgid="6484461562647915707">"আপোনাৰ ফ\'নত <xliff:g id="APP_NAME">%1$s</xliff:g> ইনষ্টল কৰিব পৰা নগ\'ল৷"</string>
@@ -44,21 +44,21 @@
<string name="manage_applications" msgid="5400164782453975580">"এপ্ পৰিচালনা"</string>
<string name="out_of_space_dlg_title" msgid="4156690013884649502">"খালী ঠাই নাই"</string>
<string name="out_of_space_dlg_text" msgid="8727714096031856231">"<xliff:g id="APP_NAME">%1$s</xliff:g> ইনষ্টল কৰিব পৰা নগ\'ল। কিছু খালী ঠাই উলিয়াই আকৌ চেষ্টা কৰক৷"</string>
- <string name="app_not_found_dlg_title" msgid="5107924008597470285">"এপটো পোৱা নগ\'ল"</string>
- <string name="app_not_found_dlg_text" msgid="5219983779377811611">"ইনষ্টল কৰি ৰখা এপৰ তালিকাত এই এপটো পোৱা নগ\'ল।"</string>
+ <string name="app_not_found_dlg_title" msgid="5107924008597470285">"এপ্‌টো পোৱা নগ\'ল"</string>
+ <string name="app_not_found_dlg_text" msgid="5219983779377811611">"ইনষ্টল কৰি ৰখা এপৰ তালিকাত এই এপ্‌টো পোৱা নগ\'ল।"</string>
<string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"অনুমতি নাই"</string>
<string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"বর্তমানৰ ব্যৱহাৰকাৰীজনক এইটো আনইনষ্টল কৰিবলৈ অনুমতি দিয়া হোৱা নাই।"</string>
<string name="generic_error_dlg_title" msgid="5863195085927067752">"আসোঁৱাহ"</string>
<string name="generic_error_dlg_text" msgid="5287861443265795232">"এপ্ আনইনষ্টল কৰিব পৰা নগ\'ল।"</string>
<string name="uninstall_application_title" msgid="4045420072401428123">"এপ্ আনইনষ্টল কৰক"</string>
<string name="uninstall_update_title" msgid="824411791011583031">"আপডে’ট আনইনষ্টল কৰক"</string>
- <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> হৈছে তলৰ এপটোৰ এটা অংশ:"</string>
+ <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> হৈছে তলৰ এপ্‌টোৰ এটা অংশ:"</string>
<string name="uninstall_application_text" msgid="3816830743706143980">"আপুনি এই এপ্‌টো আনইনষ্টল কৰিব বিচাৰে নেকি?"</string>
- <string name="uninstall_application_text_all_users" msgid="575491774380227119">"আপুনি "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ বাবে এই এপটো আনইনষ্টল কৰিব বিচাৰেনে? এপ্লিকেশ্বন আৰু ইয়াৰ ডেটা ডিভাইচটোত থকা "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ পৰা আঁতৰোৱা হ\'ব৷"</string>
- <string name="uninstall_application_text_user" msgid="498072714173920526">"আপুনি ব্যৱহাৰকাৰীৰ <xliff:g id="USERNAME">%1$s</xliff:g> বাবে এই এপটো আনইনষ্টল কৰিব বিচাৰেনে?"</string>
+ <string name="uninstall_application_text_all_users" msgid="575491774380227119">"আপুনি "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ বাবে এই এপ্‌টো আনইনষ্টল কৰিব বিচাৰেনে? এপ্লিকেশ্বন আৰু ইয়াৰ ডেটা ডিভাইচটোত থকা "<b>"সকলো"</b>" ব্যৱহাৰকাৰীৰ পৰা আঁতৰোৱা হ\'ব৷"</string>
+ <string name="uninstall_application_text_user" msgid="498072714173920526">"আপুনি ব্যৱহাৰকাৰীৰ <xliff:g id="USERNAME">%1$s</xliff:g> বাবে এই এপ্‌টো আনইনষ্টল কৰিব বিচাৰেনে?"</string>
<string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"আপুনি নিজৰ কৰ্মস্থানৰ প্ৰ’ফাইলৰ পৰা এই এপ্‌টো আনইনষ্টল কৰিব বিচাৰেনে?"</string>
<string name="uninstall_update_text" msgid="863648314632448705">"এই এপ্‌টোৰ ফেক্টৰী সংস্কৰণ ব্যৱহাৰ কৰিব বিচাৰেনে? আটাইবোৰ ডেটা মচা হ\'ব।"</string>
- <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"এই এপটোৰ ফেক্টৰী সংস্কৰণ ব্যৱহাৰ কৰিব বিচাৰেনে? সকলো ডেটা মচা হ\'ব। কর্মস্থানৰ প্ৰফাইল থকা ব্যৱহাৰকাৰীৰ লগতে ডিভাইচটোৰ সকলো ব্যৱহাৰকাৰীৰ ওপৰত ইয়াৰ প্ৰভাৱ পৰিব।"</string>
+ <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"এই এপ্‌টোৰ ফেক্টৰী সংস্কৰণ ব্যৱহাৰ কৰিব বিচাৰেনে? সকলো ডেটা মচা হ\'ব। কর্মস্থানৰ প্ৰফাইল থকা ব্যৱহাৰকাৰীৰ লগতে ডিভাইচটোৰ সকলো ব্যৱহাৰকাৰীৰ ওপৰত ইয়াৰ প্ৰভাৱ পৰিব।"</string>
<string name="uninstall_keep_data" msgid="7002379587465487550">"এপৰ ডেটাৰ <xliff:g id="SIZE">%1$s</xliff:g> ৰাখক"</string>
<string name="uninstalling_notification_channel" msgid="840153394325714653">"আনইনষ্টল কৰি থকা হৈছে"</string>
<string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"যিবোৰ আনইনষ্টল পৰা নগ\'ল"</string>
@@ -70,9 +70,9 @@
<string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> আনইনষ্টল কৰিব পৰা নগ\'ল।"</string>
<string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"ডিভাইচৰ সক্ৰিয় প্ৰশাসক এপ্ আনইনষ্টল কৰিব নোৱাৰি"</string>
<string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g>ৰ সক্ৰিয় ডিভাইচৰ প্ৰশাসকীয় এপ্ আনইনষ্টল কৰিব নোৱাৰি"</string>
- <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"এই এপটো কিছুসংখ্যক ব্যৱহাৰকাৰী বা প্ৰ\'ফাইলৰ বাবে প্ৰয়োজনীয় আৰু বাকীসকলৰ বাবে ইয়াক আনইনষ্টল কৰা হৈছে"</string>
- <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"আপোনাৰ প্ৰ\'ফাইলৰ বাবে এই এপটোৰ প্ৰয়োজন আছে গতিকে আনইনষ্টল কৰিব পৰা নাযায়।"</string>
- <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"এই এপটো আনইনষ্টল কৰিব পৰা নাযায় কাৰণ আপোনাৰ ডিভাইচৰ প্ৰশাসকে এই এপ্ ৰখাটো বাধ্যতামূলক কৰি ৰাখিছে।"</string>
+ <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"এই এপ্‌টো কিছুসংখ্যক ব্যৱহাৰকাৰী বা প্ৰ\'ফাইলৰ বাবে প্ৰয়োজনীয় আৰু বাকীসকলৰ বাবে ইয়াক আনইনষ্টল কৰা হৈছে"</string>
+ <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"আপোনাৰ প্ৰ\'ফাইলৰ বাবে এই এপ্‌টোৰ প্ৰয়োজন আছে গতিকে আনইনষ্টল কৰিব পৰা নাযায়।"</string>
+ <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"এই এপ্‌টো আনইনষ্টল কৰিব পৰা নাযায় কাৰণ আপোনাৰ ডিভাইচৰ প্ৰশাসকে এই এপ্ ৰখাটো বাধ্যতামূলক কৰি ৰাখিছে।"</string>
<string name="manage_device_administrators" msgid="3092696419363842816">"ডিভাইচৰ প্ৰশাসক এপসমূহ পৰিচালনা কৰক"</string>
<string name="manage_users" msgid="1243995386982560813">"ব্যৱহাৰকাৰী পৰিচালনা কৰক"</string>
<string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> আনইনষ্টল কৰিব নোৱাৰি।"</string>
@@ -84,9 +84,9 @@
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ টেবলেটটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্‌ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
<string name="untrusted_external_source_warning" product="tv" msgid="7057271609532508035">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ টিভিটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্‌ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
<string name="untrusted_external_source_warning" product="default" msgid="8444191224459138919">"আপোনাৰ সুৰক্ষাৰ বাবে আপোনাৰ ফ’নটোক বৰ্তমান এই উৎসটোৰ পৰা অজ্ঞাত এপ্‌ ইনষ্টল কৰাৰ অনুমতি দিয়া হোৱা নাই। আপুনি এইটো ছেটিঙত সলনি কৰিব পাৰে।"</string>
- <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"আপোনাৰ ফ\'ন আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপটো ইনষ্টল কৰি এপটোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
- <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"আপোনাৰ টেবলেট আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপটো ইনষ্টল কৰি এপটোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
- <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"আপোনাৰ টিভি আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপটো ইনষ্টল কৰি এপটোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
+ <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"আপোনাৰ ফ\'ন আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্‌টো ইনষ্টল কৰি এপ্‌টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
+ <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"আপোনাৰ টেবলেট আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্‌টো ইনষ্টল কৰি এপ্‌টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
+ <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"আপোনাৰ টিভি আৰু ব্যক্তিগত ডেটা অজ্ঞাত এপৰ আক্ৰমণৰ বলি হোৱাৰ সম্ভাৱনা অধিক। আপুনি এই এপ্‌টো ইনষ্টল কৰি এপ্‌টোৰ ব্যৱহাৰৰ ফলত আপোনাৰ টিভিত হ\'ব পৰা যিকোনো ক্ষতি বা ডেটা ক্ষয়ৰ বাবে আপুনি নিজে দায়ী হ\'ব বুলি সন্মতি দিয়ে।"</string>
<string name="anonymous_source_continue" msgid="4375745439457209366">"অব্যাহত ৰাখক"</string>
<string name="external_sources_settings" msgid="4046964413071713807">"ছেটিং"</string>
<string name="wear_app_channel" msgid="1960809674709107850">"ৱেৰ এপসমূহ ইনষ্টল/আনইনষ্টল কৰি থকা হৈছে"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 734eb33e3003..6bb759543a52 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -209,7 +209,7 @@
<string name="tts_engine_settings_button" msgid="477155276199968948">"የፍርግም ቅንብሮችን ያስጀምሩ"</string>
<string name="tts_engine_preference_section_title" msgid="3861562305498624904">"የተመረጠ ፍርግም"</string>
<string name="tts_general_section_title" msgid="8919671529502364567">"አጠቃላይ"</string>
- <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"የንግግር ድምጽ ውፍረት ዳግም አስጀምር"</string>
+ <string name="tts_reset_speech_pitch_title" msgid="7149398585468413246">"የንግግር ድምፅ ውፍረት ዳግም አስጀምር"</string>
<string name="tts_reset_speech_pitch_summary" msgid="6822904157021406449">"ጽሑፉ የሚነገርበትን የድምጽ ውፍረት ወደ ነባሪ ዳግም አስጀምር።"</string>
<string-array name="tts_rate_entries">
<item msgid="9004239613505400644">"በጣም ቀርፋፋ"</item>
@@ -407,7 +407,7 @@
<string name="force_resizable_activities" msgid="7143612144399959606">"እንቅስቃሴዎች ዳግመኛ እንዲመጣጠኑ አስገድድ"</string>
<string name="force_resizable_activities_summary" msgid="2490382056981583062">"የዝርዝር ሰነድ እሴቶች ምንም ይሁኑ ምን ለበርካታ መስኮቶች ሁሉንም እንቅስቃሴዎች መጠናቸው የሚቀየሩ እንዲሆኑ ያደርጋቸዋል።"</string>
<string name="enable_freeform_support" msgid="7599125687603914253">"የነጻ ቅርጽ መስኮቶችን ያንቁ"</string>
- <string name="enable_freeform_support_summary" msgid="1822862728719276331">"የሙከራ ነጻ መልክ መስኮቶች ድጋፍን አንቃ"</string>
+ <string name="enable_freeform_support_summary" msgid="1822862728719276331">"የሙከራ ነፃ መልክ መስኮቶች ድጋፍን አንቃ"</string>
<string name="desktop_mode" msgid="2389067840550544462">"የዴስክቶፕ ሁነታ"</string>
<string name="local_backup_password_title" msgid="4631017948933578709">"የዴስክቶፕ መጠባበቂያ ይለፍ ቃል"</string>
<string name="local_backup_password_summary_none" msgid="7646898032616361714">"ዴስክቶፕ ሙሉ ምትኬዎች በአሁኑ ሰዓት አልተጠበቁም"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index c4e9dd471c73..976949cb6b15 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -268,7 +268,7 @@
<string name="keep_screen_on" msgid="1187161672348797558">"জাগ্ৰত কৰি ৰাখক"</string>
<string name="keep_screen_on_summary" msgid="1510731514101925829">"চ্চাৰ্জ হৈ থকাৰ সময়ত স্ক্ৰীন কেতিয়াও সুপ্ত অৱস্থালৈ নাযায়"</string>
<string name="bt_hci_snoop_log" msgid="7291287955649081448">"ব্লুটুথ HCI স্নুপ ল’গ সক্ষম কৰক"</string>
- <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"ব্লুটুথ পেকেট সংগ্ৰহ কৰক। (এই ছেটিংটো সলনি কৰাৰ পিছত ব্লুটুথ ট’গল কৰক)"</string>
+ <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"ব্লুটুথ পেকেট সংগ্ৰহ কৰক। (এই ছেটিংটো সলনি কৰাৰ পাছত ব্লুটুথ ট’গল কৰক)"</string>
<string name="oem_unlock_enable" msgid="5334869171871566731">"ঔইএম আনলক"</string>
<string name="oem_unlock_enable_summary" msgid="5857388174390953829">"বুটল\'ডাৰটো আনলক কৰিবলৈ অনুমতি দিয়ক"</string>
<string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"ঔইএম আনলক কৰাৰ অনুমতি দিবনে?"</string>
@@ -515,8 +515,8 @@
<string name="active_input_method_subtypes" msgid="4232680535471633046">"সক্ৰিয়হৈ থকা ইনপুট পদ্ধতিসমূহ"</string>
<string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"ছিষ্টেমৰ ভাষা ব্যৱহাৰ কৰক"</string>
<string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>ৰ ছেটিং খুলিব পৰা নগ\'ল"</string>
- <string name="ime_security_warning" msgid="6547562217880551450">"এই ইনপুট পদ্ধতিটোৱে আপুনি টাইপ কৰা আপোনাৰ ব্যক্তিগত ডেটা যেনে পাছৱৰ্ডসমূহ আৰু ক্ৰেডিট কাৰ্ডৰ নম্বৰসমূহকে ধৰি আটাইবোৰ পাঠ সংগ্ৰহ কৰিবলৈ সক্ষম হ\'ব পাৰে। <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> এপটোৰ লগত ই সংলগ্ন। এই ইনপুট পদ্ধতিটো ব্যৱহাৰ কৰেনে?"</string>
- <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"টোকা: ৰিবুট কৰাৰ পিছত আপুনি ফ\'নটো আনলক নকৰালৈকে এই এপটো ষ্টাৰ্ট নহ’ব"</string>
+ <string name="ime_security_warning" msgid="6547562217880551450">"এই ইনপুট পদ্ধতিটোৱে আপুনি টাইপ কৰা আপোনাৰ ব্যক্তিগত ডেটা যেনে পাছৱৰ্ডসমূহ আৰু ক্ৰেডিট কাৰ্ডৰ নম্বৰসমূহকে ধৰি আটাইবোৰ পাঠ সংগ্ৰহ কৰিবলৈ সক্ষম হ\'ব পাৰে। <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> এপ্‌টোৰ লগত ই সংলগ্ন। এই ইনপুট পদ্ধতিটো ব্যৱহাৰ কৰেনে?"</string>
+ <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"টোকা: ৰিবুট কৰাৰ পাছত আপুনি ফ\'নটো আনলক নকৰালৈকে এই এপ্‌টো ষ্টাৰ্ট নহ’ব"</string>
<string name="ims_reg_title" msgid="8197592958123671062">"আইএমএছ পঞ্জীয়ন স্থিতি"</string>
<string name="ims_reg_status_registered" msgid="884916398194885457">"পঞ্জীকৃত"</string>
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"পঞ্জীকৃত নহয়"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 98cc18e8ad13..b6eb82ef5578 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -247,7 +247,7 @@
<string name="adb_paired_devices_title" msgid="5268997341526217362">"Appareils associés"</string>
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Actuellement connecté"</string>
<string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Infos sur l\'appareil"</string>
- <string name="adb_device_forget" msgid="193072400783068417">"Supprimer"</string>
+ <string name="adb_device_forget" msgid="193072400783068417">"Retirer"</string>
<string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Empreinte de l\'appareil : <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
<string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Échec de la connexion"</string>
<string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Vérifiez que l\'appareil <xliff:g id="DEVICE_NAME">%1$s</xliff:g> est connecté au bon réseau"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 5d5ebe7a39d1..c17a1d51042e 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -509,7 +509,7 @@
<string name="screen_zoom_summary_extremely_large" msgid="1438045624562358554">"అతి పెద్దగా"</string>
<string name="screen_zoom_summary_custom" msgid="3468154096832912210">"అనుకూలం (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string>
<string name="content_description_menu_button" msgid="6254844309171779931">"మెనూ"</string>
- <string name="retail_demo_reset_message" msgid="5392824901108195463">"డెమో మోడ్‌లో ఫ్యాక్టరీ రీసెట్‌ను నిర్వహించడానికి పాస్‌వర్డ్‌ను నమోదు చేయండి"</string>
+ <string name="retail_demo_reset_message" msgid="5392824901108195463">"డెమో మోడ్‌లో ఫ్యాక్టరీ రీసెట్‌ను మేనేజ్ చేయడానికి పాస్‌వర్డ్‌ను నమోదు చేయండి"</string>
<string name="retail_demo_reset_next" msgid="3688129033843885362">"తర్వాత"</string>
<string name="retail_demo_reset_title" msgid="1866911701095959800">"పాస్‌వర్డ్ అవసరం"</string>
<string name="active_input_method_subtypes" msgid="4232680535471633046">"సక్రియ ఇన్‌పుట్ పద్ధతులు"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 5662ce6bd808..6bc1160a8d0a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -356,7 +356,7 @@ public class CachedBluetoothDeviceManager {
* @return {@code true}, if the device should pair automatically; Otherwise, return
* {@code false}.
*/
- public synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {
+ private synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {
boolean isOngoingSetMemberPair = mOngoingSetMemberPair != null;
int bondState = device.getBondState();
if (isOngoingSetMemberPair || bondState != BluetoothDevice.BOND_NONE
@@ -365,10 +365,44 @@ public class CachedBluetoothDeviceManager {
+ " , device.getBondState: " + bondState);
return false;
}
+ return true;
+ }
- Log.d(TAG, "Bond " + device.getName() + " by CSIP");
+ /**
+ * Called when we found a set member of a group. The function will check the {@code groupId} if
+ * it exists and the bond state of the device is BOND_NONE, and if there isn't any ongoing pair
+ * , and then pair the device automatically.
+ *
+ * @param device The found device
+ * @param groupId The group id of the found device
+ */
+ public synchronized void pairDeviceByCsip(BluetoothDevice device, int groupId) {
+ if (!shouldPairByCsip(device, groupId)) {
+ return;
+ }
+ Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " by CSIP");
mOngoingSetMemberPair = device;
- return true;
+ syncConfigFromMainDevice(device, groupId);
+ device.createBond(BluetoothDevice.TRANSPORT_LE);
+ }
+
+ private void syncConfigFromMainDevice(BluetoothDevice device, int groupId) {
+ if (!isOngoingPairByCsip(device)) {
+ return;
+ }
+ CachedBluetoothDevice memberDevice = findDevice(device);
+ CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(memberDevice);
+ if (mainDevice == null) {
+ mainDevice = mCsipDeviceManager.getCachedDevice(groupId);
+ }
+
+ if (mainDevice == null || mainDevice.equals(memberDevice)) {
+ Log.d(TAG, "no mainDevice");
+ return;
+ }
+
+ // The memberDevice set PhonebookAccessPermission
+ device.setPhonebookAccessPermission(mainDevice.getDevice().getPhonebookAccessPermission());
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index d5de3f0525a0..20a6cd8e09ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -101,7 +101,14 @@ public class CsipDeviceManager {
return groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
}
- private CachedBluetoothDevice getCachedDevice(int groupId) {
+ /**
+ * To find the device with {@code groupId}.
+ *
+ * @param groupId The group id
+ * @return if we could find a device with this {@code groupId} return this device. Otherwise,
+ * return null.
+ */
+ public CachedBluetoothDevice getCachedDevice(int groupId) {
log("getCachedDevice: groupId: " + groupId);
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
index 39977dfa5c80..f969a63dc663 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java
@@ -41,19 +41,19 @@ public class MobileNetworkTypeIconsTest {
MobileNetworkTypeIcon icon =
MobileNetworkTypeIcons.getNetworkTypeIcon(TelephonyIcons.FOUR_G);
- assertThat(icon.getName()).isEqualTo(TelephonyIcons.H_PLUS.name);
+ assertThat(icon.getName()).isEqualTo(TelephonyIcons.FOUR_G.name);
assertThat(icon.getIconResId()).isEqualTo(TelephonyIcons.ICON_4G);
}
@Test
public void getNetworkTypeIcon_unknown_returnsUnknown() {
- SignalIcon.MobileIconGroup unknownGroup =
- new SignalIcon.MobileIconGroup("testUnknownNameHere", 45, 6);
+ SignalIcon.MobileIconGroup unknownGroup = new SignalIcon.MobileIconGroup(
+ "testUnknownNameHere", /* dataContentDesc= */ 45, /* dataType= */ 6);
MobileNetworkTypeIcon icon = MobileNetworkTypeIcons.getNetworkTypeIcon(unknownGroup);
assertThat(icon.getName()).isEqualTo("testUnknownNameHere");
- assertThat(icon.getIconResId()).isEqualTo(45);
- assertThat(icon.getContentDescriptionResId()).isEqualTo(6);
+ assertThat(icon.getIconResId()).isEqualTo(6);
+ assertThat(icon.getContentDescriptionResId()).isEqualTo(45);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 62552f914459..61802a87361c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -582,4 +582,24 @@ public class CachedBluetoothDeviceManagerTest {
assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isTrue();
assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isFalse();
}
+
+ @Test
+ public void pairDeviceByCsip_device2AndCapGroup1_device2StartsPairing() {
+ doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
+ when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mDevice1.getPhonebookAccessPermission()).thenReturn(BluetoothDevice.ACCESS_ALLOWED);
+ CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+ assertThat(cachedDevice1).isNotNull();
+ when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+ CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+ assertThat(cachedDevice2).isNotNull();
+
+ int groupId = CAP_GROUP1.keySet().stream().findFirst().orElse(
+ BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+ assertThat(groupId).isNotEqualTo(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+ mCachedDeviceManager.pairDeviceByCsip(mDevice2, groupId);
+
+ verify(mDevice2).setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+ verify(mDevice2).createBond(BluetoothDevice.TRANSPORT_LE);
+ }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index fd7554f11873..528af2ec2528 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -376,9 +376,11 @@ final class SettingsState {
Setting newSetting = new Setting(name, oldSetting.getValue(), null,
oldSetting.getPackageName(), oldSetting.getTag(), false,
oldSetting.getId());
- mSettings.put(name, newSetting);
- updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue,
+ int newSize = getNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue,
newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue());
+ checkNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
+ mSettings.put(name, newSetting);
+ updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
scheduleWriteIfNeededLocked();
}
}
@@ -410,6 +412,12 @@ final class SettingsState {
Setting oldState = mSettings.get(name);
String oldValue = (oldState != null) ? oldState.value : null;
String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
+ String newDefaultValue = makeDefault ? value : oldDefaultValue;
+
+ int newSize = getNewMemoryUsagePerPackageLocked(packageName, oldValue, value,
+ oldDefaultValue, newDefaultValue);
+ checkNewMemoryUsagePerPackageLocked(packageName, newSize);
+
Setting newState;
if (oldState != null) {
@@ -430,8 +438,7 @@ final class SettingsState {
addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState);
- updateMemoryUsagePerPackageLocked(packageName, oldValue, value,
- oldDefaultValue, newState.getDefaultValue());
+ updateMemoryUsagePerPackageLocked(packageName, newSize);
scheduleWriteIfNeededLocked();
@@ -552,13 +559,14 @@ final class SettingsState {
}
Setting oldState = mSettings.remove(name);
+ int newSize = getNewMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
+ null, oldState.defaultValue, null);
FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, /* value= */ "",
/* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey),
FrameworkStatsLog.SETTING_CHANGED__REASON__DELETED);
- updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
- null, oldState.defaultValue, null);
+ updateMemoryUsagePerPackageLocked(oldState.packageName, newSize);
addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState);
@@ -579,16 +587,18 @@ final class SettingsState {
Setting oldSetting = new Setting(setting);
String oldValue = setting.getValue();
String oldDefaultValue = setting.getDefaultValue();
+ String newValue = oldDefaultValue;
+ String newDefaultValue = oldDefaultValue;
+
+ int newSize = getNewMemoryUsagePerPackageLocked(setting.packageName, oldValue,
+ newValue, oldDefaultValue, newDefaultValue);
+ checkNewMemoryUsagePerPackageLocked(setting.packageName, newSize);
if (!setting.reset()) {
return false;
}
- String newValue = setting.getValue();
- String newDefaultValue = setting.getDefaultValue();
-
- updateMemoryUsagePerPackageLocked(setting.packageName, oldValue,
- newValue, oldDefaultValue, newDefaultValue);
+ updateMemoryUsagePerPackageLocked(setting.packageName, newSize);
addHistoricalOperationLocked(HISTORICAL_OPERATION_RESET, oldSetting);
@@ -696,38 +706,49 @@ final class SettingsState {
}
@GuardedBy("mLock")
- private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue,
- String newValue, String oldDefaultValue, String newDefaultValue) {
- if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) {
- return;
- }
+ private boolean isExemptFromMemoryUsageCap(String packageName) {
+ return mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED
+ || SYSTEM_PACKAGE_NAME.equals(packageName);
+ }
- if (SYSTEM_PACKAGE_NAME.equals(packageName)) {
+ @GuardedBy("mLock")
+ private void checkNewMemoryUsagePerPackageLocked(String packageName, int newSize)
+ throws IllegalStateException {
+ if (isExemptFromMemoryUsageCap(packageName)) {
return;
}
+ if (newSize > mMaxBytesPerAppPackage) {
+ throw new IllegalStateException("You are adding too many system settings. "
+ + "You should stop using system settings for app specific data"
+ + " package: " + packageName);
+ }
+ }
+ @GuardedBy("mLock")
+ private int getNewMemoryUsagePerPackageLocked(String packageName, String oldValue,
+ String newValue, String oldDefaultValue, String newDefaultValue) {
+ if (isExemptFromMemoryUsageCap(packageName)) {
+ return 0;
+ }
+ final Integer currentSize = mPackageToMemoryUsage.get(packageName);
final int oldValueSize = (oldValue != null) ? oldValue.length() : 0;
final int newValueSize = (newValue != null) ? newValue.length() : 0;
final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0;
final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0;
final int deltaSize = newValueSize + newDefaultValueSize
- oldValueSize - oldDefaultValueSize;
+ return Math.max((currentSize != null) ? currentSize + deltaSize : deltaSize, 0);
+ }
- Integer currentSize = mPackageToMemoryUsage.get(packageName);
- final int newSize = Math.max((currentSize != null)
- ? currentSize + deltaSize : deltaSize, 0);
-
- if (newSize > mMaxBytesPerAppPackage) {
- throw new IllegalStateException("You are adding too many system settings. "
- + "You should stop using system settings for app specific data"
- + " package: " + packageName);
+ @GuardedBy("mLock")
+ private void updateMemoryUsagePerPackageLocked(String packageName, int newSize) {
+ if (isExemptFromMemoryUsageCap(packageName)) {
+ return;
}
-
if (DEBUG) {
Slog.i(LOG_TAG, "Settings for package: " + packageName
+ " size: " + newSize + " bytes.");
}
-
mPackageToMemoryUsage.put(packageName, newSize);
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 69eb7133f46f..66b809aeae30 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -20,6 +20,8 @@ import android.test.AndroidTestCase;
import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.google.common.base.Strings;
+
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -276,4 +278,40 @@ public class SettingsStateTest extends AndroidTestCase {
settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
return settingsState;
}
+
+ public void testInsertSetting_memoryUsage() {
+ SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ // No exception should be thrown when there is no cap
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+ null, false, "p1");
+ settingsState.deleteSettingLocked(SETTING_NAME);
+
+ settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+ // System package doesn't have memory usage limit
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+ null, false, SYSTEM_PACKAGE);
+ settingsState.deleteSettingLocked(SETTING_NAME);
+
+ // Should not throw if usage is under the cap
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 19999),
+ null, false, "p1");
+ settingsState.deleteSettingLocked(SETTING_NAME);
+ try {
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+ null, false, "p1");
+ fail("Should throw because it exceeded per package memory usage");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().contains("p1"));
+ }
+ try {
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+ null, false, "p1");
+ fail("Should throw because it exceeded per package memory usage");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().contains("p1"));
+ }
+ assertTrue(settingsState.getSettingLocked(SETTING_NAME).isNull());
+ }
}
diff --git a/packages/SoundPicker/res/values-am/strings.xml b/packages/SoundPicker/res/values-am/strings.xml
index 07aee8a646ec..85206c0b46a0 100644
--- a/packages/SoundPicker/res/values-am/strings.xml
+++ b/packages/SoundPicker/res/values-am/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="ringtone_default" msgid="798836092118824500">"ነባሪ የስልክ ላይ ጥሪ"</string>
- <string name="notification_sound_default" msgid="8133121186242636840">"ነባሪ የማሳወቂያ ድምጽ"</string>
+ <string name="notification_sound_default" msgid="8133121186242636840">"ነባሪ የማሳወቂያ ድምፅ"</string>
<string name="alarm_sound_default" msgid="4787646764557462649">"ነባሪ የማንቂያ ድምፅ"</string>
<string name="add_ringtone_text" msgid="6642389991738337529">"የጥሪ ቅላጼ አክል"</string>
<string name="add_alarm_text" msgid="3545497316166999225">"የማንቂያ ደውል አክል"</string>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2737ecf5ffa6..b5145f926abd 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -402,6 +402,9 @@
android:permission="com.android.systemui.permission.SELF"
android:exported="false" />
+ <service android:name=".screenshot.ScreenshotCrossProfileService"
+ android:permission="com.android.systemui.permission.SELF"
+ android:exported="false" />
<service android:name=".screenrecord.RecordingService" />
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 23cee4d0972d..fdfad2bc2fa1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -25,7 +25,6 @@ import android.graphics.Rect
import android.os.Looper
import android.util.Log
import android.util.MathUtils
-import android.view.GhostView
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
@@ -86,6 +85,9 @@ constructor(
*/
val sourceIdentity: Any
+ /** The CUJ associated to this controller. */
+ val cuj: DialogCuj?
+
/**
* Move the drawing of the source in the overlay of [viewGroup].
*
@@ -142,7 +144,31 @@ constructor(
* controlled by this controller.
*/
// TODO(b/252723237): Make this non-nullable
- fun jankConfigurationBuilder(cuj: Int): InteractionJankMonitor.Configuration.Builder?
+ fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder?
+
+ companion object {
+ /**
+ * Create a [Controller] that can animate [source] to and from a dialog.
+ *
+ * Important: The view must be attached to a [ViewGroup] when calling this function and
+ * during the animation. For safety, this method will return null when it is not.
+ *
+ * Note: The background of [view] should be a (rounded) rectangle so that it can be
+ * properly animated.
+ */
+ fun fromView(source: View, cuj: DialogCuj? = null): Controller? {
+ if (source.parent !is ViewGroup) {
+ Log.e(
+ TAG,
+ "Skipping animation as view $source is not attached to a ViewGroup",
+ Exception(),
+ )
+ return null
+ }
+
+ return ViewDialogLaunchAnimatorController(source, cuj)
+ }
+ }
}
/**
@@ -172,7 +198,12 @@ constructor(
cuj: DialogCuj? = null,
animateBackgroundBoundsChange: Boolean = false
) {
- show(dialog, createController(view), cuj, animateBackgroundBoundsChange)
+ val controller = Controller.fromView(view, cuj)
+ if (controller == null) {
+ dialog.show()
+ } else {
+ show(dialog, controller, animateBackgroundBoundsChange)
+ }
}
/**
@@ -187,10 +218,10 @@ constructor(
* Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
* made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
*/
+ @JvmOverloads
fun show(
dialog: Dialog,
controller: Controller,
- cuj: DialogCuj? = null,
animateBackgroundBoundsChange: Boolean = false
) {
if (Looper.myLooper() != Looper.getMainLooper()) {
@@ -207,7 +238,10 @@ constructor(
it.dialog.window.decorView.viewRootImpl == controller.viewRoot
}
val animateFrom =
- animatedParent?.dialogContentWithBackground?.let { createController(it) } ?: controller
+ animatedParent?.dialogContentWithBackground?.let {
+ Controller.fromView(it, controller.cuj)
+ }
+ ?: controller
if (animatedParent == null && animateFrom !is LaunchableView) {
// Make sure the View we launch from implements LaunchableView to avoid visibility
@@ -244,96 +278,12 @@ constructor(
animateBackgroundBoundsChange,
animatedParent,
isForTesting,
- cuj,
)
openedDialogs.add(animatedDialog)
animatedDialog.start()
}
- /** Create a [Controller] that can animate [source] to & from a dialog. */
- private fun createController(source: View): Controller {
- return object : Controller {
- override val viewRoot: ViewRootImpl
- get() = source.viewRootImpl
-
- override val sourceIdentity: Any = source
-
- override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
- // Create a temporary ghost of the source (which will make it invisible) and add it
- // to the host dialog.
- GhostView.addGhost(source, viewGroup)
-
- // The ghost of the source was just created, so the source is currently invisible.
- // We need to make sure that it stays invisible as long as the dialog is shown or
- // animating.
- (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
- }
-
- override fun stopDrawingInOverlay() {
- // Note: here we should remove the ghost from the overlay, but in practice this is
- // already done by the launch controllers created below.
-
- // Make sure we allow the source to change its visibility again.
- (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
- source.visibility = View.VISIBLE
- }
-
- override fun createLaunchController(): LaunchAnimator.Controller {
- val delegate = GhostedViewLaunchAnimatorController(source)
- return object : LaunchAnimator.Controller by delegate {
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
- // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
- // ghost (that ghosts only the source content, and not its background) will
- // be added right after this by the delegate and will be animated.
- GhostView.removeGhost(source)
- delegate.onLaunchAnimationStart(isExpandingFullyAbove)
- }
-
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
-
- // We hide the source when the dialog is showing. We will make this view
- // visible again when dismissing the dialog. This does nothing if the source
- // implements [LaunchableView], as it's already INVISIBLE in that case.
- source.visibility = View.INVISIBLE
- }
- }
- }
-
- override fun createExitController(): LaunchAnimator.Controller {
- return GhostedViewLaunchAnimatorController(source)
- }
-
- override fun shouldAnimateExit(): Boolean {
- // The source should be invisible by now, if it's not then something else changed
- // its visibility and we probably don't want to run the animation.
- if (source.visibility != View.INVISIBLE) {
- return false
- }
-
- return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
- }
-
- override fun onExitAnimationCancelled() {
- // Make sure we allow the source to change its visibility again.
- (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
-
- // If the view is invisible it's probably because of us, so we make it visible
- // again.
- if (source.visibility == View.INVISIBLE) {
- source.visibility = View.VISIBLE
- }
- }
-
- override fun jankConfigurationBuilder(
- cuj: Int
- ): InteractionJankMonitor.Configuration.Builder? {
- return InteractionJankMonitor.Configuration.Builder.withView(cuj, source)
- }
- }
- }
-
/**
* Launch [dialog] from [another dialog][animateFrom] that was shown using [show]. This will
* allow for dismissing the whole stack.
@@ -563,9 +513,6 @@ private class AnimatedDialog(
* Whether synchronization should be disabled, which can be useful if we are running in a test.
*/
private val forceDisableSynchronization: Boolean,
-
- /** Interaction to which the dialog animation is associated. */
- private val cuj: DialogCuj? = null
) {
/**
* The DecorView of this dialog window.
@@ -618,8 +565,9 @@ private class AnimatedDialog(
private var hasInstrumentedJank = false
fun start() {
+ val cuj = controller.cuj
if (cuj != null) {
- val config = controller.jankConfigurationBuilder(cuj.cujType)
+ val config = controller.jankConfigurationBuilder()
if (config != null) {
if (cuj.tag != null) {
config.setTag(cuj.tag)
@@ -865,7 +813,7 @@ private class AnimatedDialog(
return
}
- ViewRootSync.synchronizeNextDraw(decorView, controller.viewRoot.view, then)
+ ViewRootSync.synchronizeNextDraw(controller.viewRoot.view, decorView, then)
decorView.invalidate()
controller.viewRoot.view.invalidate()
}
@@ -917,7 +865,7 @@ private class AnimatedDialog(
}
if (hasInstrumentedJank) {
- interactionJankMonitor.end(cuj!!.cujType)
+ interactionJankMonitor.end(controller.cuj!!.cujType)
}
}
)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index 8ce372dbb278..40a5e9794d37 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -30,7 +30,12 @@ interface Expandable {
*/
fun activityLaunchController(cujType: Int? = null): ActivityLaunchAnimator.Controller?
- // TODO(b/230830644): Introduce DialogLaunchAnimator and a function to expose it here.
+ /**
+ * Create a [DialogLaunchAnimator.Controller] that can be used to expand this [Expandable] into
+ * a Dialog, or return `null` if this [Expandable] should not be animated (e.g. if it is
+ * currently not attached or visible).
+ */
+ fun dialogLaunchController(cuj: DialogCuj? = null): DialogLaunchAnimator.Controller?
companion object {
/**
@@ -39,6 +44,7 @@ interface Expandable {
* Note: The background of [view] should be a (rounded) rectangle so that it can be properly
* animated.
*/
+ @JvmStatic
fun fromView(view: View): Expandable {
return object : Expandable {
override fun activityLaunchController(
@@ -46,6 +52,12 @@ interface Expandable {
): ActivityLaunchAnimator.Controller? {
return ActivityLaunchAnimator.Controller.fromView(view, cujType)
}
+
+ override fun dialogLaunchController(
+ cuj: DialogCuj?
+ ): DialogLaunchAnimator.Controller? {
+ return DialogLaunchAnimator.Controller.fromView(view, cuj)
+ }
}
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index f79b328190dd..5f1bb83715c2 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -89,6 +89,11 @@ class TextAnimator(
var y: Float = 0f
/**
+ * The current line of text being drawn, in a multi-line TextView.
+ */
+ var lineNo: Int = 0
+
+ /**
* Mutable text size of the glyph in pixels.
*/
var textSize: Float = 0f
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index d427a57f3b87..0448c818f765 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -244,7 +244,7 @@ class TextInterpolator(
canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
run.fontRuns.forEach { fontRun ->
- drawFontRun(canvas, run, fontRun, tmpPaint)
+ drawFontRun(canvas, run, fontRun, lineNo, tmpPaint)
}
} finally {
canvas.restore()
@@ -349,7 +349,7 @@ class TextInterpolator(
var glyphFilter: GlyphCallback? = null
// Draws single font run.
- private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) {
+ private fun drawFontRun(c: Canvas, line: Run, run: FontRun, lineNo: Int, paint: Paint) {
var arrayIndex = 0
val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress)
@@ -368,11 +368,13 @@ class TextInterpolator(
tmpGlyph.font = font
tmpGlyph.runStart = run.start
tmpGlyph.runLength = run.end - run.start
+ tmpGlyph.lineNo = lineNo
tmpPaintForGlyph.set(paint)
var prevStart = run.start
for (i in run.start until run.end) {
+ tmpGlyph.glyphIndex = i
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)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
new file mode 100644
index 000000000000..ecee598afe4e
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation
+
+import android.view.GhostView
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewRootImpl
+import com.android.internal.jank.InteractionJankMonitor
+
+/** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
+class ViewDialogLaunchAnimatorController
+internal constructor(
+ private val source: View,
+ override val cuj: DialogCuj?,
+) : DialogLaunchAnimator.Controller {
+ override val viewRoot: ViewRootImpl
+ get() = source.viewRootImpl
+
+ override val sourceIdentity: Any = source
+
+ override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+ // Create a temporary ghost of the source (which will make it invisible) and add it
+ // to the host dialog.
+ GhostView.addGhost(source, viewGroup)
+
+ // The ghost of the source was just created, so the source is currently invisible.
+ // We need to make sure that it stays invisible as long as the dialog is shown or
+ // animating.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+ }
+
+ override fun stopDrawingInOverlay() {
+ // Note: here we should remove the ghost from the overlay, but in practice this is
+ // already done by the launch controllers created below.
+
+ // Make sure we allow the source to change its visibility again.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
+ source.visibility = View.VISIBLE
+ }
+
+ override fun createLaunchController(): LaunchAnimator.Controller {
+ val delegate = GhostedViewLaunchAnimatorController(source)
+ return object : LaunchAnimator.Controller by delegate {
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
+ // ghost (that ghosts only the source content, and not its background) will
+ // be added right after this by the delegate and will be animated.
+ GhostView.removeGhost(source)
+ delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+
+ // We hide the source when the dialog is showing. We will make this view
+ // visible again when dismissing the dialog. This does nothing if the source
+ // implements [LaunchableView], as it's already INVISIBLE in that case.
+ source.visibility = View.INVISIBLE
+ }
+ }
+ }
+
+ override fun createExitController(): LaunchAnimator.Controller {
+ return GhostedViewLaunchAnimatorController(source)
+ }
+
+ override fun shouldAnimateExit(): Boolean {
+ // The source should be invisible by now, if it's not then something else changed
+ // its visibility and we probably don't want to run the animation.
+ if (source.visibility != View.INVISIBLE) {
+ return false
+ }
+
+ return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true)
+ }
+
+ override fun onExitAnimationCancelled() {
+ // Make sure we allow the source to change its visibility again.
+ (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
+
+ // If the view is invisible it's probably because of us, so we make it visible
+ // again.
+ if (source.visibility == View.INVISIBLE) {
+ source.visibility = View.VISIBLE
+ }
+ }
+
+ override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
+ val type = cuj?.cujType ?: return null
+ return InteractionJankMonitor.Configuration.Builder.withView(type, source)
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 1b7e26b0aea0..58ffef25cb42 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -360,7 +360,9 @@ class ViewHierarchyAnimator {
* [interpolator] and [duration].
*
* The end state of the animation is controlled by [destination]. This value can be any of
- * the four corners, any of the four edges, or the center of the view.
+ * the four corners, any of the four edges, or the center of the view. If any margins are
+ * added on the side(s) of the [destination], the translation of those margins can be
+ * included by specifying [includeMargins].
*
* @param onAnimationEnd an optional runnable that will be run once the animation finishes
* successfully. Will not be run if the animation is cancelled.
@@ -371,6 +373,7 @@ class ViewHierarchyAnimator {
destination: Hotspot = Hotspot.CENTER,
interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR,
duration: Long = DEFAULT_DURATION,
+ includeMargins: Boolean = false,
onAnimationEnd: Runnable? = null,
): Boolean {
if (
@@ -428,10 +431,12 @@ class ViewHierarchyAnimator {
val endValues =
processEndValuesForRemoval(
destination,
+ rootView,
rootView.left,
rootView.top,
rootView.right,
- rootView.bottom
+ rootView.bottom,
+ includeMargins,
)
val boundsToAnimate = mutableSetOf<Bound>()
@@ -718,70 +723,111 @@ class ViewHierarchyAnimator {
* | | -> | | -> | | -> x---x -> x
* | | x-------x x-----x
* x---------x
+ * 4) destination=TOP, includeMargins=true (and view has large top margin)
+ * x---------x
+ * x---------x
+ * x---------x x---------x
+ * x---------x | |
+ * x---------x | | x---------x
+ * | | | |
+ * | | -> x---------x -> -> ->
+ * | |
+ * x---------x
* ```
*/
private fun processEndValuesForRemoval(
destination: Hotspot,
+ rootView: View,
left: Int,
top: Int,
right: Int,
- bottom: Int
+ bottom: Int,
+ includeMargins: Boolean = false,
): 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 marginAdjustment =
+ if (includeMargins &&
+ (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
+ val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams
+ DimenHolder(
+ left = marginLp.leftMargin,
+ top = marginLp.topMargin,
+ right = marginLp.rightMargin,
+ bottom = marginLp.bottomMargin
+ )
+ } else {
+ DimenHolder(0, 0, 0, 0)
+ }
- return mapOf(
- Bound.LEFT to endLeft,
- Bound.TOP to endTop,
- Bound.RIGHT to endRight,
- Bound.BOTTOM to endBottom
- )
+ // These are the end values to use *if* this bound is part of the destination.
+ val endLeft = left - marginAdjustment.left
+ val endTop = top - marginAdjustment.top
+ val endRight = right + marginAdjustment.right
+ val endBottom = bottom + marginAdjustment.bottom
+
+ // For the below calculations: We need to ensure that the destination bound and the
+ // bound *opposite* to the destination bound end at the same value, to ensure that the
+ // view has size 0 for that dimension.
+ // For example,
+ // - If destination=TOP, then endTop == endBottom. Left and right stay the same.
+ // - If destination=RIGHT, then endRight == endLeft. Top and bottom stay the same.
+ // - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight.
+
+ return when (destination) {
+ Hotspot.TOP -> mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.LEFT to left,
+ Bound.RIGHT to right,
+ )
+ Hotspot.TOP_RIGHT -> mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ )
+ Hotspot.RIGHT -> mapOf(
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ Bound.TOP to top,
+ Bound.BOTTOM to bottom,
+ )
+ Hotspot.BOTTOM_RIGHT -> mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ )
+ Hotspot.BOTTOM -> mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.LEFT to left,
+ Bound.RIGHT to right,
+ )
+ Hotspot.BOTTOM_LEFT -> mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ )
+ Hotspot.LEFT -> mapOf(
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ Bound.TOP to top,
+ Bound.BOTTOM to bottom,
+ )
+ Hotspot.TOP_LEFT -> mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ )
+ Hotspot.CENTER -> mapOf(
+ Bound.LEFT to (endLeft + endRight) / 2,
+ Bound.RIGHT to (endLeft + endRight) / 2,
+ Bound.TOP to (endTop + endBottom) / 2,
+ Bound.BOTTOM to (endTop + endBottom) / 2,
+ )
+ }
}
/**
@@ -1061,4 +1107,12 @@ class ViewHierarchyAnimator {
abstract fun setValue(view: View, value: Int)
abstract fun getValue(view: View): Int
}
+
+ /** Simple data class to hold a set of dimens for left, top, right, bottom. */
+ private data class DimenHolder(
+ val left: Int,
+ val top: Int,
+ val right: Int,
+ val bottom: Int,
+ )
}
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index 8457312dc403..cf66ff60f9ab 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -40,6 +40,10 @@ java_test_host {
"tests/**/*.kt",
"tests/**/*.java",
],
+ data: [
+ ":framework",
+ ":androidx.annotation_annotation",
+ ],
static_libs: [
"SystemUILintChecker",
"junit",
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
index 4eeeb850292a..4b9aa13c0240 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
@@ -32,7 +32,8 @@ import org.jetbrains.uast.UReferenceExpression
class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
override fun getApplicableReferenceNames(): List<String> {
- return mutableListOf("ALPHA_8", "RGB_565", "ARGB_8888", "RGBA_F16", "RGBA_1010102")
+ return mutableListOf(
+ "ALPHA_8", "RGB_565", "ARGB_4444", "ARGB_8888", "RGBA_F16", "RGBA_1010102")
}
override fun visitReference(
@@ -40,13 +41,12 @@ class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
reference: UReferenceExpression,
referenced: PsiElement
) {
-
val evaluator = context.evaluator
if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) {
context.report(
ISSUE,
referenced,
- context.getNameLocation(referenced),
+ context.getNameLocation(reference),
"Replace software bitmap with `Config.HARDWARE`"
)
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
new file mode 100644
index 000000000000..1db072548a76
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+private const val CLASS_SETTINGS = "android.provider.Settings"
+
+/**
+ * Detects usage of static methods in android.provider.Settings and suggests to use an injected
+ * settings provider instance instead.
+ */
+@Suppress("UnstableApiUsage")
+class StaticSettingsProviderDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableMethodNames(): List<String> {
+ return listOf(
+ "getFloat",
+ "getInt",
+ "getLong",
+ "getString",
+ "getUriFor",
+ "putFloat",
+ "putInt",
+ "putLong",
+ "putString"
+ )
+ }
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ val evaluator = context.evaluator
+ val className = method.containingClass?.qualifiedName
+ if (
+ className != "$CLASS_SETTINGS.Global" &&
+ className != "$CLASS_SETTINGS.Secure" &&
+ className != "$CLASS_SETTINGS.System"
+ ) {
+ return
+ }
+ if (!evaluator.isStatic(method)) {
+ return
+ }
+
+ val subclassName = className.substring(CLASS_SETTINGS.length + 1)
+
+ context.report(
+ ISSUE,
+ method,
+ context.getNameLocation(node),
+ "`@Inject` a ${subclassName}Settings instead"
+ )
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "StaticSettingsProvider",
+ briefDescription = "Static settings provider usage",
+ explanation =
+ """
+ Static settings provider methods, such as `Settings.Global.putInt()`, should \
+ not be used because they make testing difficult. Instead, use an injected \
+ settings provider. For example, instead of calling `Settings.Secure.getInt()`, \
+ annotate the class constructor with `@Inject` and add `SecureSettings` to the \
+ parameters.
+ """,
+ category = Category.CORRECTNESS,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(
+ StaticSettingsProviderDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index cf7c1b5e44a2..3f334c1cdb9c 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -36,6 +36,7 @@ class SystemUIIssueRegistry : IssueRegistry() {
RegisterReceiverViaContextDetector.ISSUE,
SoftwareBitmapDetector.ISSUE,
NonInjectedServiceDetector.ISSUE,
+ StaticSettingsProviderDetector.ISSUE
)
override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
index 486af9dd5d98..141dd0535986 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -18,6 +18,8 @@ package com.android.internal.systemui.lint
import com.android.annotations.NonNull
import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+import com.android.tools.lint.checks.infrastructure.TestFiles.LibraryReferenceTestFile
+import java.io.File
import org.intellij.lang.annotations.Language
@Suppress("UnstableApiUsage")
@@ -30,132 +32,8 @@ private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(sourc
*/
internal val androidStubs =
arrayOf(
- indentedJava(
- """
-package android.app;
-
-public class ActivityManager {
- public static int getCurrentUser() {}
-}
-"""
- ),
- indentedJava(
- """
-package android.accounts;
-
-public class AccountManager {
- public static AccountManager get(Context context) { return null; }
-}
-"""
- ),
- indentedJava(
- """
-package android.os;
-import android.content.pm.UserInfo;
-import android.annotation.UserIdInt;
-
-public class UserManager {
- public UserInfo getUserInfo(@UserIdInt int userId) {}
-}
-"""
- ),
- indentedJava("""
-package android.annotation;
-
-public @interface UserIdInt {}
-"""),
- indentedJava("""
-package android.content.pm;
-
-public class UserInfo {}
-"""),
- indentedJava("""
-package android.os;
-
-public class Looper {}
-"""),
- indentedJava("""
-package android.os;
-
-public class Handler {}
-"""),
- indentedJava("""
-package android.content;
-
-public class ServiceConnection {}
-"""),
- indentedJava("""
-package android.os;
-
-public enum UserHandle {
- ALL
-}
-"""),
- indentedJava(
- """
-package android.content;
-import android.os.UserHandle;
-import android.os.Handler;
-import android.os.Looper;
-import java.util.concurrent.Executor;
-
-public class Context {
- public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {}
- public void registerReceiverAsUser(
- BroadcastReceiver receiver, UserHandle user, IntentFilter filter,
- String broadcastPermission, Handler scheduler) {}
- public void registerReceiverForAllUsers(
- BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission,
- Handler scheduler) {}
- public void sendBroadcast(Intent intent) {}
- public void sendBroadcast(Intent intent, String receiverPermission) {}
- public void sendBroadcastAsUser(Intent intent, UserHandle userHandle, String permission) {}
- public void bindService(Intent intent) {}
- public void bindServiceAsUser(
- Intent intent, ServiceConnection connection, int flags, UserHandle userHandle) {}
- public void unbindService(ServiceConnection connection) {}
- public Looper getMainLooper() { return null; }
- public Executor getMainExecutor() { return null; }
- public Handler getMainThreadHandler() { return null; }
- public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { return null; }
- public abstract @Nullable Object getSystemService(@ServiceName @NonNull String name);
-}
-"""
- ),
- indentedJava(
- """
-package android.app;
-import android.content.Context;
-
-public class Activity extends Context {}
-"""
- ),
- indentedJava(
- """
-package android.graphics;
-
-public class Bitmap {
- public enum Config {
- ARGB_8888,
- RGB_565,
- HARDWARE
- }
- public static Bitmap createBitmap(int width, int height, Config config) {
- return null;
- }
-}
-"""
- ),
- indentedJava("""
-package android.content;
-
-public class BroadcastReceiver {}
-"""),
- indentedJava("""
-package android.content;
-
-public class IntentFilter {}
-"""),
+ LibraryReferenceTestFile(File("framework.jar").canonicalFile),
+ LibraryReferenceTestFile(File("androidx.annotation_annotation.jar").canonicalFile),
indentedJava(
"""
package com.android.systemui.settings;
@@ -167,23 +45,4 @@ public interface UserTracker {
}
"""
),
- indentedJava(
- """
-package androidx.annotation;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-import static java.lang.annotation.ElementType.CONSTRUCTOR;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.ElementType.TYPE;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-@Retention(SOURCE)
-@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
-public @interface WorkerThread {
-}
-"""
- ),
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index 6ae8fd3f25a1..c35ac61a6543 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class BindServiceOnMainThreadDetectorTest : LintDetectorTest() {
+class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = BindServiceOnMainThreadDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> = listOf(BindServiceOnMainThreadDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 7d422807ae08..376acb56fac9 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class BroadcastSentViaContextDetectorTest : LintDetectorTest() {
+class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = BroadcastSentViaContextDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> = listOf(BroadcastSentViaContextDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index c468af8d09e0..301c338f9b42 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class NonInjectedMainThreadDetectorTest : LintDetectorTest() {
+class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = NonInjectedMainThreadDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> = listOf(NonInjectedMainThreadDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index c83a35b46ca6..0a74bfcfee57 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class NonInjectedServiceDetectorTest : LintDetectorTest() {
+class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = NonInjectedServiceDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> = listOf(NonInjectedServiceDetector.ISSUE)
@Test
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index ebcddebfbc28..9ed7aa029b1d 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
+class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> = listOf(RegisterReceiverViaContextDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index b03a11c4f02f..54cac7b35598 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class SlowUserQueryDetectorTest : LintDetectorTest() {
+class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = SlowUserQueryDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> =
listOf(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index fb6537e92d15..c632636eb9c8 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -16,18 +16,15 @@
package com.android.internal.systemui.lint
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFiles
-import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
@Suppress("UnstableApiUsage")
-class SoftwareBitmapDetectorTest : LintDetectorTest() {
+class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = SoftwareBitmapDetector()
- override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
override fun getIssues(): List<Issue> = listOf(SoftwareBitmapDetector.ISSUE)
@@ -54,12 +51,12 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/android/graphics/Bitmap.java:5: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
- ARGB_8888,
- ~~~~~~~~~
- src/android/graphics/Bitmap.java:6: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
- RGB_565,
- ~~~~~~~
+ src/TestClass.java:5: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
+ Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
+ ~~~~~~~
+ src/TestClass.java:6: Warning: Replace software bitmap with Config.HARDWARE [SoftwareBitmap]
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+ ~~~~~~~~~
0 errors, 2 warnings
"""
)
@@ -70,7 +67,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() {
lint()
.files(
TestFiles.java(
- """
+ """
import android.graphics.Bitmap;
public class TestClass {
@@ -79,8 +76,7 @@ class SoftwareBitmapDetectorTest : LintDetectorTest() {
}
}
"""
- )
- .indented(),
+ ),
*stubs
)
.issues(SoftwareBitmapDetector.ISSUE)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
new file mode 100644
index 000000000000..b83ed7067bc3
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
+
+ override fun getDetector(): Detector = StaticSettingsProviderDetector()
+ override fun getIssues(): List<Issue> = listOf(StaticSettingsProviderDetector.ISSUE)
+
+ @Test
+ fun testGetServiceWithString() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+
+ import android.provider.Settings;
+ import android.provider.Settings.Global;
+ import android.provider.Settings.Secure;
+
+ public class TestClass {
+ public void getSystemServiceWithoutDagger(Context context) {
+ final ContentResolver cr = mContext.getContentResolver();
+ Global.getFloat(cr, Settings.Global.UNLOCK_SOUND);
+ Global.getInt(cr, Settings.Global.UNLOCK_SOUND);
+ Global.getLong(cr, Settings.Global.UNLOCK_SOUND);
+ Global.getString(cr, Settings.Global.UNLOCK_SOUND);
+ Global.getFloat(cr, Settings.Global.UNLOCK_SOUND, 1f);
+ Global.getInt(cr, Settings.Global.UNLOCK_SOUND, 1);
+ Global.getLong(cr, Settings.Global.UNLOCK_SOUND, 1L);
+ Global.getString(cr, Settings.Global.UNLOCK_SOUND, "1");
+ Global.putFloat(cr, Settings.Global.UNLOCK_SOUND, 1f);
+ Global.putInt(cr, Settings.Global.UNLOCK_SOUND, 1);
+ Global.putLong(cr, Settings.Global.UNLOCK_SOUND, 1L);
+ Global.putString(cr, Settings.Global.UNLOCK_SOUND, "1");
+
+ Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f);
+ Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
+ Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L);
+ Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1");
+ Secure.putFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f);
+ Secure.putInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
+ Secure.putLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L);
+ Secure.putString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1");
+
+ Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f);
+ Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1);
+ Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L);
+ Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT, "1");
+ Settings.System.putFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f);
+ Settings.System.putInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1);
+ Settings.System.putLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L);
+ Settings.System.putString(cr, Settings.Global.UNLOCK_SOUND, "1");
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(StaticSettingsProviderDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:10: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getFloat(cr, Settings.Global.UNLOCK_SOUND);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:11: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getInt(cr, Settings.Global.UNLOCK_SOUND);
+ ~~~~~~
+ src/test/pkg/TestClass.java:12: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getLong(cr, Settings.Global.UNLOCK_SOUND);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:13: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getString(cr, Settings.Global.UNLOCK_SOUND);
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:14: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getFloat(cr, Settings.Global.UNLOCK_SOUND, 1f);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:15: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getInt(cr, Settings.Global.UNLOCK_SOUND, 1);
+ ~~~~~~
+ src/test/pkg/TestClass.java:16: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getLong(cr, Settings.Global.UNLOCK_SOUND, 1L);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:17: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.getString(cr, Settings.Global.UNLOCK_SOUND, "1");
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:18: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.putFloat(cr, Settings.Global.UNLOCK_SOUND, 1f);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:19: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.putInt(cr, Settings.Global.UNLOCK_SOUND, 1);
+ ~~~~~~
+ src/test/pkg/TestClass.java:20: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.putLong(cr, Settings.Global.UNLOCK_SOUND, 1L);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:21: Warning: @Inject a GlobalSettings instead [StaticSettingsProvider]
+ Global.putString(cr, Settings.Global.UNLOCK_SOUND, "1");
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:23: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:24: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ ~~~~~~
+ src/test/pkg/TestClass.java:25: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:26: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED);
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:27: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:28: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
+ ~~~~~~
+ src/test/pkg/TestClass.java:29: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:30: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.getString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1");
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:31: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.putFloat(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1f);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:32: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.putInt(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1);
+ ~~~~~~
+ src/test/pkg/TestClass.java:33: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.putLong(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, 1L);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:34: Warning: @Inject a SecureSettings instead [StaticSettingsProvider]
+ Secure.putString(cr, Settings.Secure.ASSIST_GESTURE_ENABLED, "1");
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:36: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:37: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ ~~~~~~
+ src/test/pkg/TestClass.java:38: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:39: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT);
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:40: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:41: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1);
+ ~~~~~~
+ src/test/pkg/TestClass.java:42: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:43: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.getString(cr, Settings.System.SCREEN_OFF_TIMEOUT, "1");
+ ~~~~~~~~~
+ src/test/pkg/TestClass.java:44: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.putFloat(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1f);
+ ~~~~~~~~
+ src/test/pkg/TestClass.java:45: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.putInt(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1);
+ ~~~~~~
+ src/test/pkg/TestClass.java:46: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.putLong(cr, Settings.System.SCREEN_OFF_TIMEOUT, 1L);
+ ~~~~~~~
+ src/test/pkg/TestClass.java:47: Warning: @Inject a SystemSettings instead [StaticSettingsProvider]
+ Settings.System.putString(cr, Settings.Global.UNLOCK_SOUND, "1");
+ ~~~~~~~~~
+ 0 errors, 36 warnings
+ """
+ )
+ }
+
+ private val stubs = androidStubs
+}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
new file mode 100644
index 000000000000..3f93f075fe8b
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
@@ -0,0 +1,48 @@
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import java.io.File
+import org.junit.ClassRule
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.model.Statement
+
+@Suppress("UnstableApiUsage")
+@RunWith(JUnit4::class)
+abstract class SystemUILintDetectorTest : LintDetectorTest() {
+
+ companion object {
+ @ClassRule
+ @JvmField
+ val libraryChecker: LibraryExists =
+ LibraryExists("framework.jar", "androidx.annotation_annotation.jar")
+ }
+
+ class LibraryExists(vararg val libraryNames: String) : TestRule {
+ override fun apply(base: Statement, description: Description): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ for (libName in libraryNames) {
+ val libFile = File(libName)
+ if (!libFile.canonicalFile.exists()) {
+ throw Exception(
+ "Could not find $libName in the test's working directory. " +
+ "File ${libFile.absolutePath} does not exist."
+ )
+ }
+ }
+ base.evaluate()
+ }
+ }
+ }
+ }
+ /**
+ * Customize the lint task to disable SDK usage completely. This ensures that running the tests
+ * in Android Studio has the same result as running the tests in atest
+ */
+ override fun lint(): TestLintTask =
+ super.lint().allowMissingSdk(true).sdkHome(File("/dev/null"))
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
index 065c3149c2f5..50c3d7e1e76b 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
@@ -40,17 +40,16 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.animation.LaunchAnimator
import kotlin.math.roundToInt
-/** A controller that can control animated launches. */
+/** A controller that can control animated launches from an [Expandable]. */
interface ExpandableController {
- /** Create an [ActivityLaunchAnimator.Controller] to animate into an Activity. */
- fun forActivity(): ActivityLaunchAnimator.Controller
-
- /** Create a [DialogLaunchAnimator.Controller] to animate into a Dialog. */
- fun forDialog(): DialogLaunchAnimator.Controller
+ /** The [Expandable] controlled by this controller. */
+ val expandable: Expandable
}
/**
@@ -120,13 +119,26 @@ internal class ExpandableControllerImpl(
private val layoutDirection: LayoutDirection,
private val isComposed: State<Boolean>,
) : ExpandableController {
- override fun forActivity(): ActivityLaunchAnimator.Controller {
- return activityController()
- }
+ override val expandable: Expandable =
+ object : Expandable {
+ override fun activityLaunchController(
+ cujType: Int?,
+ ): ActivityLaunchAnimator.Controller? {
+ if (!isComposed.value) {
+ return null
+ }
- override fun forDialog(): DialogLaunchAnimator.Controller {
- return dialogController()
- }
+ return activityController(cujType)
+ }
+
+ override fun dialogLaunchController(cuj: DialogCuj?): DialogLaunchAnimator.Controller? {
+ if (!isComposed.value) {
+ return null
+ }
+
+ return dialogController(cuj)
+ }
+ }
/**
* Create a [LaunchAnimator.Controller] that is going to be used to drive an activity or dialog
@@ -233,7 +245,7 @@ internal class ExpandableControllerImpl(
}
/** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */
- private fun activityController(): ActivityLaunchAnimator.Controller {
+ private fun activityController(cujType: Int?): ActivityLaunchAnimator.Controller {
val delegate = launchController()
return object : ActivityLaunchAnimator.Controller, LaunchAnimator.Controller by delegate {
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -248,10 +260,11 @@ internal class ExpandableControllerImpl(
}
}
- private fun dialogController(): DialogLaunchAnimator.Controller {
+ private fun dialogController(cuj: DialogCuj?): DialogLaunchAnimator.Controller {
return object : DialogLaunchAnimator.Controller {
override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl
override val sourceIdentity: Any = this@ExpandableControllerImpl
+ override val cuj: DialogCuj? = cuj
override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
val newOverlay = viewGroup.overlay as ViewGroupOverlay
@@ -294,9 +307,7 @@ internal class ExpandableControllerImpl(
isDialogShowing.value = false
}
- override fun jankConfigurationBuilder(
- cuj: Int
- ): InteractionJankMonitor.Configuration.Builder? {
+ override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
// TODO(b/252723237): Add support for jank monitoring when animating from a
// Composable.
return null
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 491ec20c519d..da612a9276a3 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -189,45 +189,8 @@
-packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt
-packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
-packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
--packages/SystemUI/src/com/android/systemui/media/AnimationBindHandler.kt
--packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
--packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt
--packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt
--packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
--packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt
--packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt
--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
--packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
--packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
--packages/SystemUI/src/com/android/systemui/media/MediaData.kt
--packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
--packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
--packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
--packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt
--packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
--packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
--packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
--packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
-packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
-packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt
--packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
--packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt
--packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
--packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
--packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
--packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
--packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt
--packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt
--packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
--packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt
--packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
--packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
--packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
--packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
--packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt
-packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
-packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
-packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -246,8 +209,6 @@
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
-packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt
-packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -528,6 +489,8 @@
-packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
-packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
-packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+-packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+-packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
-packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
-packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
-packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
@@ -653,32 +616,11 @@
-packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt
-packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/AnimationBindHandlerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
-packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index b3dd95553ed0..dee0f5cd1979 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -205,6 +205,13 @@ enum class Style(internal val coreSpec: CoreSpec) {
n1 = TonalSpec(HueSource(), ChromaMultiple(0.0833)),
n2 = TonalSpec(HueSource(), ChromaMultiple(0.1666))
)),
+ MONOCHROMATIC(CoreSpec(
+ a1 = TonalSpec(HueSource(), ChromaConstant(.0)),
+ a2 = TonalSpec(HueSource(), ChromaConstant(.0)),
+ a3 = TonalSpec(HueSource(), ChromaConstant(.0)),
+ n1 = TonalSpec(HueSource(), ChromaConstant(.0)),
+ n2 = TonalSpec(HueSource(), ChromaConstant(.0))
+ )),
}
class ColorScheme(
@@ -219,7 +226,7 @@ class ColorScheme(
val neutral1: List<Int>
val neutral2: List<Int>
- constructor(@ColorInt seed: Int, darkTheme: Boolean):
+ constructor(@ColorInt seed: Int, darkTheme: Boolean) :
this(seed, darkTheme, Style.TONAL_SPOT)
@JvmOverloads
@@ -227,7 +234,7 @@ class ColorScheme(
wallpaperColors: WallpaperColors,
darkTheme: Boolean,
style: Style = Style.TONAL_SPOT
- ):
+ ) :
this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
val allAccentColors: List<Int>
@@ -472,4 +479,4 @@ class ColorScheme(
return huePopulation
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index cafaaf854eed..7709f210f22f 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -33,6 +33,7 @@ java_library {
static_libs: [
"androidx.annotation_annotation",
+ "error_prone_annotations",
"PluginCoreLib",
"SystemUIAnimationLib",
],
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 1e74c3d68efc..89f5c2c80e29 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -14,9 +14,11 @@
package com.android.systemui.plugins
import android.content.res.Resources
+import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.view.View
import com.android.systemui.plugins.annotations.ProvidesInterface
+import com.android.systemui.plugins.log.LogBuffer
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
@@ -69,6 +71,9 @@ interface ClockController {
/** Optional method for dumping debug information */
fun dump(pw: PrintWriter) { }
+
+ /** Optional method for debug logging */
+ fun setLogBuffer(logBuffer: LogBuffer) { }
}
/** Interface for a specific clock face version rendered by the clock */
@@ -114,6 +119,17 @@ interface ClockAnimations {
/** Runs the battery animation (if any). */
fun charge() { }
+
+ /** Move the clock, for example, if the notification tray appears in split-shade mode. */
+ fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) { }
+
+ /**
+ * Whether this clock has a custom position update animation. If true, the keyguard will call
+ * `onPositionUpdated` to notify the clock of a position update animation. If false, a default
+ * animation will be used (e.g. a simple translation).
+ */
+ val hasCustomPositionUpdatedAnimation
+ get() = false
}
/** Events that have specific data about the related face */
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
index 6124e10144f2..6436dcb5f613 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
import android.os.Trace
import android.util.Log
-import com.android.systemui.log.dagger.LogModule
-import com.android.systemui.util.collection.RingBuffer
+import com.android.systemui.plugins.util.RingBuffer
import com.google.errorprone.annotations.CompileTimeConstant
import java.io.PrintWriter
import java.util.concurrent.ArrayBlockingQueue
@@ -61,15 +60,18 @@ import kotlin.math.max
* In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or
* the first letter of any of the previous.
*
- * Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory].
+ * In SystemUI, buffers are provided by LogModule. Instances should be created using a SysUI
+ * LogBufferFactory.
*
* @param name The name of this buffer, printed when the buffer is dumped and in some other
* situations.
* @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start
- * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches
- * the maximum, it behaves like a ring buffer.
+ * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches the
+ * maximum, it behaves like a ring buffer.
*/
-class LogBuffer @JvmOverloads constructor(
+class LogBuffer
+@JvmOverloads
+constructor(
private val name: String,
private val maxSize: Int,
private val logcatEchoTracker: LogcatEchoTracker,
@@ -78,7 +80,7 @@ class LogBuffer @JvmOverloads constructor(
private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
private val echoMessageQueue: BlockingQueue<LogMessage>? =
- if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null
+ if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null
init {
if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) {
@@ -133,11 +135,11 @@ class LogBuffer @JvmOverloads constructor(
*/
@JvmOverloads
inline fun log(
- tag: String,
- level: LogLevel,
- messageInitializer: MessageInitializer,
- noinline messagePrinter: MessagePrinter,
- exception: Throwable? = null,
+ tag: String,
+ level: LogLevel,
+ messageInitializer: MessageInitializer,
+ noinline messagePrinter: MessagePrinter,
+ exception: Throwable? = null,
) {
val message = obtain(tag, level, messagePrinter, exception)
messageInitializer(message)
@@ -152,14 +154,13 @@ class LogBuffer @JvmOverloads constructor(
* log message is built during runtime, use the [LogBuffer.log] overloaded method that takes in
* an initializer and a message printer.
*
- * Log buffers are limited by the number of entries, so logging more frequently
- * will limit the time window that the LogBuffer covers in a bug report. Richer logs, on the
- * other hand, make a bug report more actionable, so using the [log] with a messagePrinter to
- * add more detail to every log may do more to improve overall logging than adding more logs
- * with this method.
+ * Log buffers are limited by the number of entries, so logging more frequently will limit the
+ * time window that the LogBuffer covers in a bug report. Richer logs, on the other hand, make a
+ * bug report more actionable, so using the [log] with a messagePrinter to add more detail to
+ * every log may do more to improve overall logging than adding more logs with this method.
*/
fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) =
- log(tag, level, {str1 = message}, { str1!! })
+ log(tag, level, { str1 = message }, { str1!! })
/**
* You should call [log] instead of this method.
@@ -172,10 +173,10 @@ class LogBuffer @JvmOverloads constructor(
*/
@Synchronized
fun obtain(
- tag: String,
- level: LogLevel,
- messagePrinter: MessagePrinter,
- exception: Throwable? = null,
+ tag: String,
+ level: LogLevel,
+ messagePrinter: MessagePrinter,
+ exception: Throwable? = null,
): LogMessage {
if (!mutable) {
return FROZEN_MESSAGE
@@ -189,8 +190,7 @@ class LogBuffer @JvmOverloads constructor(
* You should call [log] instead of this method.
*
* After acquiring a message via [obtain], call this method to signal to the buffer that you
- * have finished filling in its data fields. The message will be echoed to logcat if
- * necessary.
+ * have finished filling in its data fields. The message will be echoed to logcat if necessary.
*/
@Synchronized
fun commit(message: LogMessage) {
@@ -213,7 +213,8 @@ class LogBuffer @JvmOverloads constructor(
/** Sends message to echo after determining whether to use Logcat and/or systrace. */
private fun echoToDesiredEndpoints(message: LogMessage) {
- val includeInLogcat = logcatEchoTracker.isBufferLoggable(name, message.level) ||
+ val includeInLogcat =
+ logcatEchoTracker.isBufferLoggable(name, message.level) ||
logcatEchoTracker.isTagLoggable(message.tag, message.level)
echo(message, toLogcat = includeInLogcat, toSystrace = systrace)
}
@@ -221,7 +222,12 @@ class LogBuffer @JvmOverloads constructor(
/** Converts the entire buffer to a newline-delimited string */
@Synchronized
fun dump(pw: PrintWriter, tailLength: Int) {
- val iterationStart = if (tailLength <= 0) { 0 } else { max(0, buffer.size - tailLength) }
+ val iterationStart =
+ if (tailLength <= 0) {
+ 0
+ } else {
+ max(0, buffer.size - tailLength)
+ }
for (i in iterationStart until buffer.size) {
buffer[i].dump(pw)
@@ -229,9 +235,9 @@ class LogBuffer @JvmOverloads constructor(
}
/**
- * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called.
- * Calls to [log], [obtain], and [commit] will not affect the buffer and will return dummy
- * values if necessary.
+ * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called. Calls
+ * to [log], [obtain], and [commit] will not affect the buffer and will return dummy values if
+ * necessary.
*/
@Synchronized
fun freeze() {
@@ -241,9 +247,7 @@ class LogBuffer @JvmOverloads constructor(
}
}
- /**
- * Undoes the effects of calling [freeze].
- */
+ /** Undoes the effects of calling [freeze]. */
@Synchronized
fun unfreeze() {
if (frozen) {
@@ -265,8 +269,11 @@ class LogBuffer @JvmOverloads constructor(
}
private fun echoToSystrace(message: LogMessage, strMessage: String) {
- Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events",
- "$name - ${message.level.shortString} ${message.tag}: $strMessage")
+ Trace.instantForTrack(
+ Trace.TRACE_TAG_APP,
+ "UI Events",
+ "$name - ${message.level.shortString} ${message.tag}: $strMessage"
+ )
}
private fun echoToLogcat(message: LogMessage, strMessage: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt
index 53f231c9f9d2..b036cf0be1d6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt
@@ -14,17 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
import android.util.Log
-/**
- * Enum version of @Log.Level
- */
-enum class LogLevel(
- @Log.Level val nativeLevel: Int,
- val shortString: String
-) {
+/** Enum version of @Log.Level */
+enum class LogLevel(@Log.Level val nativeLevel: Int, val shortString: String) {
VERBOSE(Log.VERBOSE, "V"),
DEBUG(Log.DEBUG, "D"),
INFO(Log.INFO, "I"),
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt
index dae2592e116c..9468681289bf 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
import java.io.PrintWriter
import java.text.SimpleDateFormat
@@ -29,9 +29,10 @@ import java.util.Locale
*
* When a message is logged, the code doing the logging stores data in one or more of the generic
* fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the
- * [messagePrinter] function reads the data stored in the generic fields and converts that to a human-
- * readable string. Thus, for every log type there must be a specialized initializer function that
- * stores data specific to that log type and a specialized printer function that prints that data.
+ * [messagePrinter] function reads the data stored in the generic fields and converts that to a
+ * human- readable string. Thus, for every log type there must be a specialized initializer function
+ * that stores data specific to that log type and a specialized printer function that prints that
+ * data.
*
* See [LogBuffer.log] for more information.
*/
@@ -55,9 +56,7 @@ interface LogMessage {
var bool3: Boolean
var bool4: Boolean
- /**
- * Function that dumps the [LogMessage] to the provided [writer].
- */
+ /** Function that dumps the [LogMessage] to the provided [writer]. */
fun dump(writer: PrintWriter) {
val formattedTimestamp = DATE_FORMAT.format(timestamp)
val shortLevel = level.shortString
@@ -68,12 +67,12 @@ interface LogMessage {
}
/**
- * A function that will be called if and when the message needs to be dumped to
- * logcat or a bug report. It should read the data stored by the initializer and convert it to
- * a human-readable string. The value of `this` will be the LogMessage to be printed.
- * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any
- * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance
- * of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
+ * A function that will be called if and when the message needs to be dumped to logcat or a bug
+ * report. It should read the data stored by the initializer and convert it to a human-readable
+ * string. The value of `this` will be the LogMessage to be printed. **IMPORTANT:** The printer
+ * should ONLY ever reference fields on the LogMessage and NEVER any variables in its enclosing
+ * scope. Otherwise, the runtime will need to allocate a new instance of the printer for each call,
+ * thwarting our attempts at avoiding any sort of allocation.
*/
typealias MessagePrinter = LogMessage.() -> String
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt
index 4dd6f652d1c7..f2a6a91adcdf 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
-/**
- * Recyclable implementation of [LogMessage].
- */
+/** Recyclable implementation of [LogMessage]. */
data class LogMessageImpl(
override var level: LogLevel,
override var tag: String,
@@ -68,23 +66,24 @@ data class LogMessageImpl(
companion object Factory {
fun create(): LogMessageImpl {
return LogMessageImpl(
- LogLevel.DEBUG,
- DEFAULT_TAG,
- 0,
- DEFAULT_PRINTER,
- null,
- null,
- null,
- null,
- 0,
- 0,
- 0,
- 0,
- 0.0,
- false,
- false,
- false,
- false)
+ LogLevel.DEBUG,
+ DEFAULT_TAG,
+ 0,
+ DEFAULT_PRINTER,
+ null,
+ null,
+ null,
+ null,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0.0,
+ false,
+ false,
+ false,
+ false
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt
index 8cda4236bc87..cfe894f276a0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt
@@ -14,24 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
-/**
- * Keeps track of which [LogBuffer] messages should also appear in logcat.
- */
+/** Keeps track of which [LogBuffer] messages should also appear in logcat. */
interface LogcatEchoTracker {
- /**
- * Whether [bufferName] should echo messages of [level] or higher to logcat.
- */
+ /** Whether [bufferName] should echo messages of [level] or higher to logcat. */
fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean
- /**
- * Whether [tagName] should echo messages of [level] or higher to logcat.
- */
+ /** Whether [tagName] should echo messages of [level] or higher to logcat. */
fun isTagLoggable(tagName: String, level: LogLevel): Boolean
- /**
- * Whether to log messages in a background thread.
- */
+ /** Whether to log messages in a background thread. */
val logInBackgroundThread: Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
index 40b0cdc173d8..d3fabaccb6d3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
import android.content.ContentResolver
import android.database.ContentObserver
@@ -36,19 +36,15 @@ import android.provider.Settings
* $ adb shell settings put global systemui/tag/<tag> <level>
* ```
*/
-class LogcatEchoTrackerDebug private constructor(
- private val contentResolver: ContentResolver
-) : LogcatEchoTracker {
+class LogcatEchoTrackerDebug private constructor(private val contentResolver: ContentResolver) :
+ LogcatEchoTracker {
private val cachedBufferLevels: MutableMap<String, LogLevel> = mutableMapOf()
private val cachedTagLevels: MutableMap<String, LogLevel> = mutableMapOf()
override val logInBackgroundThread = true
companion object Factory {
@JvmStatic
- fun create(
- contentResolver: ContentResolver,
- mainLooper: Looper
- ): LogcatEchoTrackerDebug {
+ fun create(contentResolver: ContentResolver, mainLooper: Looper): LogcatEchoTrackerDebug {
val tracker = LogcatEchoTrackerDebug(contentResolver)
tracker.attach(mainLooper)
return tracker
@@ -57,37 +53,35 @@ class LogcatEchoTrackerDebug private constructor(
private fun attach(mainLooper: Looper) {
contentResolver.registerContentObserver(
- Settings.Global.getUriFor(BUFFER_PATH),
- true,
- object : ContentObserver(Handler(mainLooper)) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- super.onChange(selfChange, uri)
- cachedBufferLevels.clear()
- }
- })
+ Settings.Global.getUriFor(BUFFER_PATH),
+ true,
+ object : ContentObserver(Handler(mainLooper)) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ super.onChange(selfChange, uri)
+ cachedBufferLevels.clear()
+ }
+ }
+ )
contentResolver.registerContentObserver(
- Settings.Global.getUriFor(TAG_PATH),
- true,
- object : ContentObserver(Handler(mainLooper)) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- super.onChange(selfChange, uri)
- cachedTagLevels.clear()
- }
- })
+ Settings.Global.getUriFor(TAG_PATH),
+ true,
+ object : ContentObserver(Handler(mainLooper)) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ super.onChange(selfChange, uri)
+ cachedTagLevels.clear()
+ }
+ }
+ )
}
- /**
- * Whether [bufferName] should echo messages of [level] or higher to logcat.
- */
+ /** Whether [bufferName] should echo messages of [level] or higher to logcat. */
@Synchronized
override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean {
return level.ordinal >= getLogLevel(bufferName, BUFFER_PATH, cachedBufferLevels).ordinal
}
- /**
- * Whether [tagName] should echo messages of [level] or higher to logcat.
- */
+ /** Whether [tagName] should echo messages of [level] or higher to logcat. */
@Synchronized
override fun isTagLoggable(tagName: String, level: LogLevel): Boolean {
return level >= getLogLevel(tagName, TAG_PATH, cachedTagLevels)
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt
index 1a4ad1907ff1..3c8bda4a44e0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.log
+package com.android.systemui.plugins.log
-/**
- * Production version of [LogcatEchoTracker] that isn't configurable.
- */
+/** Production version of [LogcatEchoTracker] that isn't configurable. */
class LogcatEchoTrackerProd : LogcatEchoTracker {
override val logInBackgroundThread = false
diff --git a/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt
index 97dc842ec699..68d78907f028 100644
--- a/packages/SystemUI/src/com/android/systemui/util/collection/RingBuffer.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util.collection
+package com.android.systemui.plugins.util
import kotlin.math.max
@@ -32,19 +32,16 @@ import kotlin.math.max
* @param factory A function that creates a fresh instance of T. Used by the buffer while it's
* growing to [maxSize].
*/
-class RingBuffer<T>(
- private val maxSize: Int,
- private val factory: () -> T
-) : Iterable<T> {
+class RingBuffer<T>(private val maxSize: Int, private val factory: () -> T) : Iterable<T> {
private val buffer = MutableList<T?>(maxSize) { null }
/**
* An abstract representation that points to the "end" of the buffer. Increments every time
- * [advance] is called and never wraps. Use [indexOf] to calculate the associated index into
- * the backing array. Always points to the "next" available slot in the buffer. Before the
- * buffer has completely filled, the value pointed to will be null. Afterward, it will be the
- * value at the "beginning" of the buffer.
+ * [advance] is called and never wraps. Use [indexOf] to calculate the associated index into the
+ * backing array. Always points to the "next" available slot in the buffer. Before the buffer
+ * has completely filled, the value pointed to will be null. Afterward, it will be the value at
+ * the "beginning" of the buffer.
*
* This value is unlikely to overflow. Assuming [advance] is called at rate of 100 calls/ms,
* omega will overflow after a little under three million years of continuous operation.
@@ -60,24 +57,23 @@ class RingBuffer<T>(
/**
* Advances the buffer's position by one and returns the value that is now present at the "end"
- * of the buffer. If the buffer is not yet full, uses [factory] to create a new item.
- * Otherwise, reuses the value that was previously at the "beginning" of the buffer.
+ * of the buffer. If the buffer is not yet full, uses [factory] to create a new item. Otherwise,
+ * reuses the value that was previously at the "beginning" of the buffer.
*
- * IMPORTANT: The value is returned as-is, without being reset. It will retain any data that
- * was previously stored on it.
+ * IMPORTANT: The value is returned as-is, without being reset. It will retain any data that was
+ * previously stored on it.
*/
fun advance(): T {
val index = indexOf(omega)
omega += 1
- val entry = buffer[index] ?: factory().also {
- buffer[index] = it
- }
+ val entry = buffer[index] ?: factory().also { buffer[index] = it }
return entry
}
/**
* Returns the value stored at [index], which can range from 0 (the "start", or oldest element
- * of the buffer) to [size] - 1 (the "end", or newest element of the buffer).
+ * of the buffer) to [size]
+ * - 1 (the "end", or newest element of the buffer).
*/
operator fun get(index: Int): T {
if (index < 0 || index >= size) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/plugin/tests/log/LogBufferTest.kt
index 56aff3c2fc8b..a39b856f0f49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
+++ b/packages/SystemUI/plugin/tests/log/LogBufferTest.kt
@@ -2,6 +2,7 @@ package com.android.systemui.log
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.log.LogBuffer
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
@@ -18,8 +19,7 @@ class LogBufferTest : SysuiTestCase() {
private lateinit var outputWriter: StringWriter
- @Mock
- private lateinit var logcatEchoTracker: LogcatEchoTracker
+ @Mock private lateinit var logcatEchoTracker: LogcatEchoTracker
@Before
fun setup() {
@@ -67,15 +67,17 @@ class LogBufferTest : SysuiTestCase() {
@Test
fun dump_writesCauseAndStacktrace() {
buffer = createBuffer()
- val exception = createTestException("Exception message",
+ val exception =
+ createTestException(
+ "Exception message",
"TestClass",
- cause = createTestException("The real cause!", "TestClass"))
+ cause = createTestException("The real cause!", "TestClass")
+ )
buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
val dumpedString = dumpBuffer()
- assertThat(dumpedString)
- .contains("Caused by: java.lang.RuntimeException: The real cause!")
+ assertThat(dumpedString).contains("Caused by: java.lang.RuntimeException: The real cause!")
assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:1)")
assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:2)")
}
@@ -85,49 +87,47 @@ class LogBufferTest : SysuiTestCase() {
buffer = createBuffer()
val exception = RuntimeException("Root exception message")
exception.addSuppressed(
- createTestException(
- "First suppressed exception",
- "FirstClass",
- createTestException("Cause of suppressed exp", "ThirdClass")
- ))
- exception.addSuppressed(
- createTestException("Second suppressed exception", "SecondClass"))
+ createTestException(
+ "First suppressed exception",
+ "FirstClass",
+ createTestException("Cause of suppressed exp", "ThirdClass")
+ )
+ )
+ exception.addSuppressed(createTestException("Second suppressed exception", "SecondClass"))
buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
val dumpedStr = dumpBuffer()
// first suppressed exception
assertThat(dumpedStr)
- .contains("Suppressed: " +
- "java.lang.RuntimeException: First suppressed exception")
+ .contains("Suppressed: " + "java.lang.RuntimeException: First suppressed exception")
assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:1)")
assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:2)")
assertThat(dumpedStr)
- .contains("Caused by: java.lang.RuntimeException: Cause of suppressed exp")
+ .contains("Caused by: java.lang.RuntimeException: Cause of suppressed exp")
assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:1)")
assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:2)")
// second suppressed exception
assertThat(dumpedStr)
- .contains("Suppressed: " +
- "java.lang.RuntimeException: Second suppressed exception")
+ .contains("Suppressed: " + "java.lang.RuntimeException: Second suppressed exception")
assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:1)")
assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:2)")
}
private fun createTestException(
- message: String,
- errorClass: String,
- cause: Throwable? = null,
+ message: String,
+ errorClass: String,
+ cause: Throwable? = null,
): Exception {
val exception = RuntimeException(message, cause)
- exception.stackTrace = (1..5).map { lineNumber ->
- StackTraceElement(errorClass,
- "TestMethod",
- "$errorClass.java",
- lineNumber)
- }.toTypedArray()
+ exception.stackTrace =
+ (1..5)
+ .map { lineNumber ->
+ StackTraceElement(errorClass, "TestMethod", "$errorClass.java", lineNumber)
+ }
+ .toTypedArray()
return exception
}
diff --git a/packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml b/packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml
new file mode 100644
index 000000000000..de0e526a97c3
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size android:height="@dimen/bouncer_user_switcher_popup_items_divider_height"/>
+ <solid android:color="@color/user_switcher_fullscreen_bg"/>
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 3ad7c8c4369c..d64587dcf362 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -37,6 +37,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
+ android:clipChildren="false"
android:visibility="gone" />
<!-- Not quite optimal but needed to translate these items as a group. The
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 16a1d944c4d3..647abee9a99b 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -27,6 +27,7 @@
systemui:layout_constraintEnd_toEndOf="parent"
systemui:layout_constraintTop_toTopOf="parent"
android:layout_marginHorizontal="@dimen/status_view_margin_horizontal"
+ android:clipChildren="false"
android:layout_width="0dp"
android:layout_height="wrap_content">
<LinearLayout
diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml
index d5e84f9e30eb..d5552f6ad44f 100644
--- a/packages/SystemUI/res-keyguard/values-af/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-af/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon word vereis nadat toestel herbegin het"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN word vereis nadat toestel herbegin het"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wagwoord word vereis nadat toestel herbegin het"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Patroon word vir bykomende sekuriteit vereis"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN word vir bykomende sekuriteit vereis"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Wagwoord word vir bykomende sekuriteit vereis"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Toestel is deur administrateur gesluit"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Toestel is handmatig gesluit"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nie herken nie"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Verstek"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Borrel"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml
index be52c448181d..533e5a299e4c 100644
--- a/packages/SystemUI/res-keyguard/values-am/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-am/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"መሣሪያ ዳግም ከጀመረ በኋላ ሥርዓተ ጥለት ያስፈልጋል"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"መሣሪያ ዳግም ከተነሳ በኋላ ፒን ያስፈልጋል"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"መሣሪያ ዳግም ከጀመረ በኋላ የይለፍ ቃል ያስፈልጋል"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ሥርዓተ ጥለት ለተጨማሪ ደህንነት ያስፈልጋል"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ፒን ለተጨማሪ ደህንነት ያስፈልጋል"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"የይለፍ ቃል ለተጨማሪ ደህንነት ያስፈልጋል"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"መሣሪያ በአስተዳዳሪ ተቆልፏል"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"መሣሪያ በተጠቃሚው ራሱ ተቆልፏል"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"አልታወቀም"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ነባሪ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"አረፋ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"አናሎግ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index adb57b6d4d67..81ce7d3c9361 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"يجب رسم النقش بعد إعادة تشغيل الجهاز"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"يجب إدخال رقم التعريف الشخصي بعد إعادة تشغيل الجهاز"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"يجب إدخال كلمة المرور بعد إعادة تشغيل الجهاز"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"يجب رسم النقش لمزيد من الأمان"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"يجب إدخال رقم التعريف الشخصي لمزيد من الأمان"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"يجب إدخال كلمة المرور لمزيد من الأمان"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"اختار المشرف قفل الجهاز"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"تم حظر الجهاز يدويًا"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"لم يتم التعرّف عليه."</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"تلقائي"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"فقاعة"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ساعة تقليدية"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml
index b22655af4c74..443f666c0faa 100644
--- a/packages/SystemUI/res-keyguard/values-as/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-as/strings.xml
@@ -64,9 +64,9 @@
<string name="kg_sim_unlock_progress_dialog_message" msgid="4251352015304070326">"ছিম কার্ড আনলক কৰি থকা হৈছে…"</string>
<string name="kg_invalid_sim_pin_hint" msgid="2762202646949552978">"৪টাৰ পৰা ৮টা সংখ্যাযুক্ত এটা পিন লিখক।"</string>
<string name="kg_invalid_sim_puk_hint" msgid="5319756880543857694">"PUK ক\'ডটো ৮টা বা তাতকৈ অধিক সংখ্যা থকা হ\'ব লাগিব।"</string>
- <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
+ <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"আপুনি আপোনাৰ পিন <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
<string name="kg_too_many_failed_password_attempts_dialog_message" msgid="190984061975729494">"আপুনি আপোনাৰ পাছৱৰ্ড <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ লিখিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
- <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"আপুনি আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string>
+ <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"আপুনি আপোনাৰ আনলক আৰ্হি <xliff:g id="NUMBER_0">%1$d</xliff:g> বাৰ ভুলকৈ আঁকিছে। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g>ছেকেণ্ডৰ পাছত আকৌ চেষ্টা কৰক।"</string>
<string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"ছিমৰ ভুল পিন ক\'ড, আপোনাৰ ডিভাইচটো আনলক কৰিবলৈ আপুনি এতিয়া আপোনাৰ বাহকৰ সৈতে যোগাযোগ কৰিবই লাগিব।"</string>
<string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{ছিমৰ পিন ক’ড ভুল হৈছে, আপোনাৰ ডিভাইচ আনলক কৰিবলৈ আপোনাৰ বাহকৰ লগত যোগাযোগ কৰিবই লগা হোৱাৰ পূৰ্বে আপোনাৰ ওচৰত # টা প্ৰয়াস বাকী আছে।}one{ছিমৰ পিন ক’ড ভুল হৈছে, আপোনাৰ ওচৰত # টা প্ৰয়াস বাকী আছে। }other{ছিমৰ পিন ক’ড ভুল হৈছে, আপোনাৰ ওচৰত # টা প্ৰয়াস বাকী আছে। }}"</string>
<string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"ছিম ব্যৱহাৰযোগ্য নহয়। আপোনাৰ বাহকৰ সৈতে যোগাযোগ কৰক।"</string>
@@ -75,12 +75,15 @@
<string name="kg_password_puk_failed" msgid="6778867411556937118">"ছিম PUKৰ জৰিয়তে আনলক কৰিব পৰা নগ\'ল!"</string>
<string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ইনপুট পদ্ধতি সলনি কৰক"</string>
<string name="airplane_mode" msgid="2528005343938497866">"এয়াৰপ্লে’ন ম’ড"</string>
- <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত পিন দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পিছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"অতিৰিক্ত সুৰক্ষাৰ বাবে আর্হি দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"অতিৰিক্ত সুৰক্ষাৰ বাবে পিন দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"অতিৰিক্ত সুৰক্ষাৰ বাবে পাছৱর্ড দিয়াটো বাধ্যতামূলক"</string>
+ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string>
+ <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পিন দিয়াটো বাধ্যতামূলক"</string>
+ <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"প্ৰশাসকে ডিভাইচ লক কৰি ৰাখিছে"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ডিভাইচটো মেনুৱেলভাৱে লক কৰা হৈছিল"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"চিনাক্ত কৰিব পৰা নাই"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ডিফ’ল্ট"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"এনাল’গ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml
index 6ec1061ac8bb..e12569715eca 100644
--- a/packages/SystemUI/res-keyguard/values-az/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-az/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yenidən başladıqdan sonra model tələb olunur"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıqdan sonra PIN tələb olunur"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıqdan sonra parol tələb olunur"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Əlavə təhlükəsizlik üçün model tələb olunur"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Əlavə təhlükəsizlik üçün PIN tələb olunur"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Əlavə təhlükəsizlik üçün parol tələb olunur"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Cihaz admin tərəfindən kilidlənib"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Cihaz əl ilə kilidləndi"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tanınmır"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Defolt"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Qabarcıq"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoq"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
index 13d6613f0ff4..f0d1ef2d6bc3 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Treba da unesete šablon kada se uređaj ponovo pokrene"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Treba da unesete PIN kada se uređaj ponovo pokrene"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Treba da unesete lozinku kada se uređaj ponovo pokrene"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Treba da unesete šablon radi dodatne bezbednosti"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Treba da unesete PIN radi dodatne bezbednosti"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Treba da unesete lozinku radi dodatne bezbednosti"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrator je zaključao uređaj"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznat"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Podrazumevani"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mehurići"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml
index 616d31ac6bee..e1af3eceada8 100644
--- a/packages/SystemUI/res-keyguard/values-be/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-be/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Пасля перазапуску прылады патрабуецца ўзор"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Пасля перазапуску прылады патрабуецца PIN-код"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Пасля перазапуску прылады патрабуецца пароль"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Для забеспячэння дадатковай бяспекі патрабуецца ўзор"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Для забеспячэння дадатковай бяспекі патрабуецца PIN-код"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Для забеспячэння дадатковай бяспекі патрабуецца пароль"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Прылада заблакіравана адміністратарам"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Прылада была заблакіравана ўручную"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Не распазнана"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Стандартны"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Бурбалкі"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Са стрэлкамі"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml
index 366a7f4cb817..0b4417ac30e8 100644
--- a/packages/SystemUI/res-keyguard/values-bg/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"След рестартиране на устройството се изисква фигура"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"След рестартиране на устройството се изисква ПИН код"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"След рестартиране на устройството се изисква парола"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"За допълнителна сигурност се изисква фигура"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"За допълнителна сигурност се изисква ПИН код"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"За допълнителна сигурност се изисква парола"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Устройството е заключено от администратора"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Устройството бе заключено ръчно"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Не е разпознато"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Стандартен"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Балонен"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналогов"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index c20be5d70bc7..485157984749 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইসটি পুনরায় চালু হওয়ার পর প্যাটার্নের প্রয়োজন হবে"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইসটি পুনরায় চালু হওয়ার পর পিন প্রয়োজন হবে"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইসটি পুনরায় চালু হওয়ার পর পাসওয়ার্ডের প্রয়োজন হবে"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"অতিরিক্ত সুরক্ষার জন্য প্যাটার্ন দেওয়া প্রয়োজন"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"অতিরিক্ত সুরক্ষার জন্য পিন দেওয়া প্রয়োজন"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"অতিরিক্ত সুরক্ষার জন্য পাসওয়ার্ড দেওয়া প্রয়োজন"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"প্রশাসক ডিভাইসটি লক করেছেন"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ডিভাইসটিকে ম্যানুয়ালি লক করা হয়েছে"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"শনাক্ত করা যায়নি"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ডিফল্ট"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"অ্যানালগ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index f1c00a91a734..4705b4d9645f 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Potreban je uzorak nakon što se uređaj ponovo pokrene"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Potreban je PIN nakon što se uređaj ponovo pokrene"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Potrebna je lozinka nakon što se uređaj ponovo pokrene"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Uzorak je potreban radi dodatne sigurnosti"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN je potreban radi dodatne sigurnosti"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Lozinka je potrebna radi dodatne sigurnosti"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Uređaj je zaključao administrator"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznato"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Zadano"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mjehurići"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index 709407c128e0..284eaebfbd8e 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cal introduir el patró quan es reinicia el dispositiu"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cal introduir el PIN quan es reinicia el dispositiu"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cal introduir la contrasenya quan es reinicia el dispositiu"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Cal introduir el patró per disposar de més seguretat"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Cal introduir el PIN per disposar de més seguretat"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Cal introduir la contrasenya per disposar de més seguretat"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"L\'administrador ha bloquejat el dispositiu"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositiu s\'ha bloquejat manualment"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"No s\'ha reconegut"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminada"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bombolla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analògica"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index a44658cc1de9..6b4f60742964 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po restartování zařízení je vyžadováno gesto"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po restartování zařízení je vyžadován kód PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po restartování zařízení je vyžadováno heslo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pro ještě lepší zabezpečení je vyžadováno gesto"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Pro ještě lepší zabezpečení je vyžadován kód PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Pro ještě lepší zabezpečení je vyžadováno heslo"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Zařízení je uzamknuto administrátorem"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Zařízení bylo ručně uzamčeno"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nerozpoznáno"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Výchozí"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogové"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml
index 331c35579c7b..85238dfdab0f 100644
--- a/packages/SystemUI/res-keyguard/values-da/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-da/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du skal angive et mønster, når du har genstartet enheden"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Der skal angives en pinkode efter genstart af enheden"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Der skal angives en adgangskode efter genstart af enheden"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Der kræves et mønster som ekstra beskyttelse"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Der kræves en pinkode som ekstra beskyttelse"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Der kræves en adgangskode som ekstra beskyttelse"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Enheden er blevet låst af administratoren"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheden blev låst manuelt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ikke genkendt"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Boble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml
index c19b357b5985..18befed429a9 100644
--- a/packages/SystemUI/res-keyguard/values-de/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-de/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nach dem Neustart des Geräts ist die Eingabe des Musters erforderlich"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nach dem Neustart des Geräts ist die Eingabe der PIN erforderlich"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nach dem Neustart des Geräts ist die Eingabe des Passworts erforderlich"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Zur Verbesserung der Sicherheit ist ein Muster erforderlich"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Zur Verbesserung der Sicherheit ist eine PIN erforderlich"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Zur Verbesserung der Sicherheit ist ein Passwort erforderlich"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Gerät vom Administrator gesperrt"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Gerät manuell gesperrt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nicht erkannt"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml
index 1d6ec8240a9f..65b844862a9f 100644
--- a/packages/SystemUI/res-keyguard/values-el/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-el/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Απαιτείται μοτίβο μετά από την επανεκκίνηση της συσκευής"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Απαιτείται PIN μετά από την επανεκκίνηση της συσκευής"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Απαιτείται κωδικός πρόσβασης μετά από την επανεκκίνηση της συσκευής"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Απαιτείται μοτίβο για πρόσθετη ασφάλεια"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Απαιτείται PIN για πρόσθετη ασφάλεια"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Απαιτείται κωδικός πρόσβασης για πρόσθετη ασφάλεια"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Η συσκευή κλειδώθηκε από τον διαχειριστή"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Η συσκευή κλειδώθηκε με μη αυτόματο τρόπο"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Δεν αναγνωρίστηκε"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Προεπιλογή"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Συννεφάκι"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Αναλογικό"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
index 2b78f9678415..588f1b501f53 100644
--- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index e1c253202c60..08fc8d66c98d 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
index 2b78f9678415..588f1b501f53 100644
--- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 2b78f9678415..588f1b501f53 100644
--- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
index 9052e4f04e54..a23aeb0822f3 100644
--- a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎Pattern required after device restarts‎‏‎‎‏‎"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‎PIN required after device restarts‎‏‎‎‏‎"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎Password required after device restarts‎‏‎‎‏‎"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‎‎‎‎‏‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎‎‏‎Pattern required for additional security‎‏‎‎‏‎"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‎‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎PIN required for additional security‎‏‎‎‏‎"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‏‏‎‎Password required for additional security‎‏‎‎‏‎"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎For additional security, use pattern instead‎‏‎‎‏‎"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‎‎‎For additional security, use PIN instead‎‏‎‎‏‎"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‏‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎For additional security, use password instead‎‏‎‎‏‎"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‎‏‏‏‎‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‎Device locked by admin‎‏‎‎‏‎"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‎‎‎‎Device was locked manually‎‏‎‎‏‎"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎Not recognized‎‏‎‎‏‎"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎Default‎‏‎‎‏‎"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‏‎‏‎Bubble‎‏‎‎‏‎"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎Analog‎‏‎‎‏‎"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‏‏‎‏‏‏‏‎Unlock your device to continue‎‏‎‎‏‎"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
index 9dc054a53393..c71a67865925 100644
--- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Se requiere el patrón después de reiniciar el dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Se requiere el PIN después de reiniciar el dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Se requiere la contraseña después de reiniciar el dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Se requiere el patrón por razones de seguridad"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Se requiere el PIN por razones de seguridad"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Se requiere la contraseña por razones de seguridad"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado por el administrador"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositivo se bloqueó de forma manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"No se reconoció"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Burbuja"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml
index f9f0452771af..c6ee6980ff3b 100644
--- a/packages/SystemUI/res-keyguard/values-es/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Debes introducir el patrón después de reiniciar el dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Debes introducir el PIN después de reiniciar el dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Debes introducir la contraseña después de reiniciar el dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Debes introducir el patrón como medida de seguridad adicional"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Debes introducir el PIN como medida de seguridad adicional"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Debes introducir la contraseña como medida de seguridad adicional"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado por el administrador"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositivo se ha bloqueado manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"No se reconoce"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Burbuja"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index dceb78efaca7..071ede8a0b25 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pärast seadme taaskäivitamist tuleb sisestada muster"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pärast seadme taaskäivitamist tuleb sisestada PIN-kood"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pärast seadme taaskäivitamist tuleb sisestada parool"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Lisaturvalisuse huvides tuleb sisestada muster"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Lisaturvalisuse huvides tuleb sisestada PIN-kood"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Lisaturvalisuse huvides tuleb sisestada parool"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administraator lukustas seadme"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Seade lukustati käsitsi"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ei tuvastatud"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Vaikenumbrilaud"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mull"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index 8431268464eb..9b8e65b1dde7 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Eredua marraztu beharko duzu gailua berrabiarazten denean"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PINa idatzi beharko duzu gailua berrabiarazten denean"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pasahitza idatzi beharko duzu gailua berrabiarazten denean"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Eredua behar da gailua babestuago izateko"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PINa behar da gailua babestuago izateko"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Pasahitza behar da gailua babestuago izateko"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratzaileak blokeatu egin du gailua"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Eskuz blokeatu da gailua"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ez da ezagutu"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Lehenetsia"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Puxikak"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogikoa"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index 37bb260b28d3..3583f1e60e7a 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"بعد از بازنشانی دستگاه باید الگو وارد شود"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"بعد از بازنشانی دستگاه باید پین وارد شود"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"بعد از بازنشانی دستگاه باید گذرواژه وارد شود"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"برای ایمنی بیشتر باید الگو وارد شود"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"برای ایمنی بیشتر باید پین وارد شود"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"برای ایمنی بیشتر باید گذرواژه وارد شود"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"دستگاه توسط سرپرست سیستم قفل شده است"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"دستگاه به‌صورت دستی قفل شده است"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"شناسایی نشد"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"پیش‌فرض"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"حباب"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"آنالوگ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml
index f8cec421844c..a0ac6df2f029 100644
--- a/packages/SystemUI/res-keyguard/values-fi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kuvio vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-koodi vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Salasana vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kuvio vaaditaan suojauksen parantamiseksi."</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN-koodi vaaditaan suojauksen parantamiseksi."</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Salasana vaaditaan suojauksen parantamiseksi."</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Järjestelmänvalvoja lukitsi laitteen."</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Laite lukittiin manuaalisesti"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ei tunnistettu"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Oletus"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Kupla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoginen"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
index 077fe11be4f2..66fd7c08f0e2 100644
--- a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Le schéma est exigé après le redémarrage de l\'appareil"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Le NIP est exigé après le redémarrage de l\'appareil"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Le mot de passe est exigé après le redémarrage de l\'appareil"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Le schéma est exigé pour plus de sécurité"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Le NIP est exigé pour plus de sécurité"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Le mot de passe est exigé pour plus de sécurité"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Pour plus de sécurité, utilisez plutôt un schéma"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Pour plus de sécurité, utilisez plutôt un NIP"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Pour plus de sécurité, utilisez plutôt un mot de passe"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"L\'appareil a été verrouillé par l\'administrateur"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"L\'appareil a été verrouillé manuellement"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Doigt non reconnu"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Par défaut"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bulle"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogique"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Déverrouiller votre appareil pour continuer"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index 45dadc1dfdc4..ec00ba3ae887 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Veuillez dessiner le schéma après le redémarrage de l\'appareil"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Veuillez saisir le code après le redémarrage de l\'appareil"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Veuillez saisir le mot de passe après le redémarrage de l\'appareil"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Veuillez dessiner le schéma pour renforcer la sécurité"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Veuillez saisir le code pour renforcer la sécurité"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Veuillez saisir le mot de passe pour renforcer la sécurité"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Appareil verrouillé par l\'administrateur"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Appareil verrouillé manuellement"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Non reconnu"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Par défaut"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bulle"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogique"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index 4fbdd676d23a..a3f8e86cd5e6 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"É necesario o padrón despois do reinicio do dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"É necesario o PIN despois do reinicio do dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"É necesario o contrasinal despois do reinicio do dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"É necesario o padrón para obter seguranza adicional"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"É necesario o PIN para obter seguranza adicional"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"É necesario o contrasinal para obter seguranza adicional"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"O administrador bloqueou o dispositivo"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo bloqueouse manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Non se recoñeceu"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Burbulla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analóxico"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index 6caac8a89a66..c97fe017ec60 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પૅટર્ન જરૂરી છે"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પિન જરૂરી છે"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પાસવર્ડ જરૂરી છે"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"વધારાની સુરક્ષા માટે પૅટર્ન જરૂરી છે"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"વધારાની સુરક્ષા માટે પિન જરૂરી છે"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"વધારાની સુરક્ષા માટે પાસવર્ડ જરૂરી છે"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"વ્યવસ્થાપકે ઉપકરણ લૉક કરેલું છે"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ઉપકરણ મેન્યુઅલી લૉક કર્યું હતું"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ઓળખાયેલ નથી"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ડિફૉલ્ટ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"બબલ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"એનાલોગ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 627576e1af66..128300488f27 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिवाइस फिर से चालू होने के बाद पैटर्न ज़रूरी है"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिवाइस फिर से चालू होने के बाद पिन ज़रूरी है"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिवाइस फिर से चालू होने के बाद पासवर्ड ज़रूरी है"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"अतिरिक्त सुरक्षा के लिए पैटर्न ज़रूरी है"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"अतिरिक्त सुरक्षा के लिए पिन ज़रूरी है"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"अतिरिक्त सुरक्षा के लिए पासवर्ड ज़रूरी है"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"व्यवस्थापक ने डिवाइस को लॉक किया है"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"डिवाइस को मैन्युअल रूप से लॉक किया गया था"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"पहचान नहीं हो पाई"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"डिफ़ॉल्ट"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"एनालॉग"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml
index 8b1b5042f7f6..7a14a80e9bb3 100644
--- a/packages/SystemUI/res-keyguard/values-hr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nakon ponovnog pokretanja uređaja morate unijeti uzorak"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nakon ponovnog pokretanja uređaja morate unijeti PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nakon ponovnog pokretanja uređaja morate unijeti zaporku"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Unesite uzorak radi dodatne sigurnosti"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Unesite PIN radi dodatne sigurnosti"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Unesite zaporku radi dodatne sigurnosti"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrator je zaključao uređaj"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznato"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Zadano"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mjehurić"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml
index 6b75e722dbfc..a4fbf537d331 100644
--- a/packages/SystemUI/res-keyguard/values-hu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Az eszköz újraindítását követően meg kell adni a mintát"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Az eszköz újraindítását követően meg kell adni a PIN-kódot"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Az eszköz újraindítását követően meg kell adni a jelszót"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"A nagyobb biztonság érdekében minta szükséges"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"A nagyobb biztonság érdekében PIN-kód szükséges"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"A nagyobb biztonság érdekében jelszó szükséges"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"A rendszergazda zárolta az eszközt"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Az eszközt manuálisan lezárták"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nem ismerhető fel"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Alapértelmezett"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Buborék"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analóg"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml
index 3412026b90f3..086eeb939941 100644
--- a/packages/SystemUI/res-keyguard/values-hy/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել նախշը"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել PIN կոդը"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել գաղտնաբառը"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Անվտանգության նկատառումներից ելնելով անհրաժեշտ է մուտքագրել նախշը"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Անվտանգության նկատառումներից ելնելով անհրաժեշտ է մուտքագրել PIN կոդը"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Անվտանգության նկատառումներից ելնելով անհրաժեշտ է մուտքագրել գաղտնաբառը"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Սարքը կողպված է ադմինիստրատորի կողմից"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Սարքը կողպվել է ձեռքով"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Չհաջողվեց ճանաչել"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Կանխադրված"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Պղպջակ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Անալոգային"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index 1afb7918dd83..b43a0322fc7a 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pola diperlukan setelah perangkat dimulai ulang"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah perangkat dimulai ulang"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Sandi diperlukan setelah perangkat dimulai ulang"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pola diperlukan untuk keamanan tambahan"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN diperlukan untuk keamanan tambahan"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Sandi diperlukan untuk keamanan tambahan"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Perangkat dikunci oleh admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Perangkat dikunci secara manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml
index 6abdc82ceecd..8bad961dae4b 100644
--- a/packages/SystemUI/res-keyguard/values-is/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-is/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Mynsturs er krafist þegar tækið er endurræst"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-númers er krafist þegar tækið er endurræst"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Aðgangsorðs er krafist þegar tækið er endurræst"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Mynsturs er krafist af öryggisástæðum"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN-númers er krafist af öryggisástæðum"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Aðgangsorðs er krafist af öryggisástæðum"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Kerfisstjóri læsti tæki"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Tækinu var læst handvirkt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Þekktist ekki"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Sjálfgefið"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Blaðra"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Með vísum"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml
index 9fed5f72afa1..186177ff6d49 100644
--- a/packages/SystemUI/res-keyguard/values-it/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-it/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Sequenza obbligatoria dopo il riavvio del dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN obbligatorio dopo il riavvio del dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password obbligatoria dopo il riavvio del dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Sequenza obbligatoria per maggiore sicurezza"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN obbligatorio per maggiore sicurezza"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password obbligatoria per maggiore sicurezza"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloccato dall\'amministratore"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Il dispositivo è stato bloccato manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Non riconosciuto"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predefinito"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bolla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogico"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index b5b1c533778b..aab42069590b 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"יש להזין את קו ביטול הנעילה לאחר הפעלה מחדש של המכשיר"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"צריך להזין קוד אימות לאחר הפעלה מחדש של המכשיר"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"יש להזין סיסמה לאחר הפעלה מחדש של המכשיר"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"יש להזין את קו ביטול הנעילה כדי להגביר את רמת האבטחה"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"יש להזין קוד אימות כדי להגביר את רמת האבטחה"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"יש להזין סיסמה כדי להגביר את רמת האבטחה"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"כדי להגביר את רמת האבטחה, כדאי להשתמש בקו ביטול נעילה במקום זאת"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"כדי להגביר את רמת האבטחה, כדאי להשתמש בקוד אימות במקום זאת"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"כדי להגביר את רמת האבטחה, כדאי להשתמש בסיסמה במקום זאת"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"המנהל של המכשיר נהל אותו"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"המכשיר ננעל באופן ידני"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"לא זוהתה"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"ברירת מחדל"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"בועה"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"אנלוגי"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"צריך לבטל את הנעילה של המכשיר כדי להמשיך"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml
index afe0159ca1d6..1a4fb0b8e243 100644
--- a/packages/SystemUI/res-keyguard/values-ja/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ja/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"デバイスの再起動後はパターンの入力が必要となります"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"デバイスの再起動後は PIN の入力が必要となります"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"デバイスの再起動後はパスワードの入力が必要となります"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"追加の確認のためパターンが必要です"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"追加の確認のため PIN が必要です"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"追加の確認のためパスワードが必要です"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"セキュリティを強化するには代わりにパターンを使用してください"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"セキュリティを強化するには代わりに PIN を使用してください"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"セキュリティを強化するには代わりにパスワードを使用してください"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"デバイスは管理者によりロックされています"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"デバイスは手動でロックされました"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"認識されませんでした"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"デフォルト"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"バブル"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"アナログ"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"続行するにはデバイスのロックを解除してください"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml
index b32caa75f453..123cc39e47da 100644
--- a/packages/SystemUI/res-keyguard/values-ka/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა ნიმუშის დახატვა"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა PIN-კოდის შეყვანა"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა პაროლის შეყვანა"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"დამატებითი უსაფრთხოებისთვის საჭიროა ნიმუშის დახატვა"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"დამატებითი უსაფრთხოებისთვის საჭიროა PIN-კოდის შეყვანა"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"დამატებითი უსაფრთხოებისთვის საჭიროა პაროლის შეყვანა"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"მოწყობილობა ჩაკეტილია ადმინისტრატორის მიერ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"მოწყობილობა ხელით ჩაიკეტა"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"არ არის ამოცნობილი"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ნაგულისხმევი"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ბუშტი"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ანალოგური"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml
index d6d5bcdd4beb..8daca5c6d59a 100644
--- a/packages/SystemUI/res-keyguard/values-kk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Құрылғы қайта іске қосылғаннан кейін, өрнекті енгізу қажет"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Құрылғы қайта іске қосылғаннан кейін, PIN кодын енгізу қажет"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Құрылғы қайта іске қосылғаннан кейін, құпия сөзді енгізу қажет"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Қауіпсіздікті күшейту үшін өрнекті енгізу қажет"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Қауіпсіздікті күшейту үшін PIN кодын енгізу қажет"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Қауіпсіздікті күшейту үшін құпия сөзді енгізу қажет"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Құрылғыны әкімші құлыптаған"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Құрылғы қолмен құлыпталды"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Танылмады"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Әдепкі"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Көпіршік"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналогтық"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index 00bfe05ada14..73f507c91ece 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"តម្រូវឲ្យប្រើលំនាំ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"តម្រូវឲ្យបញ្ចូលកូដ PIN បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"តម្រូវឲ្យបញ្ចូលពាក្យសម្ងាត់ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"តម្រូវឲ្យប្រើលំនាំ ដើម្បីទទួលបានសវុត្ថិភាពបន្ថែម"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"តម្រូវឲ្យបញ្ចូលកូដ PIN ដើម្បីទទួលបានសុវត្ថិភាពបន្ថែម"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"តម្រូវឲ្យបញ្ចូលពាក្យសម្ងាត់ ដើម្បីទទួលបានសុវត្ថិភាពបន្ថែម"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ឧបករណ៍​ត្រូវបាន​ចាក់សោ​ដោយអ្នក​គ្រប់គ្រង"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ឧបករណ៍ត្រូវបានចាក់សោដោយអ្នកប្រើផ្ទាល់"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"មិនអាចសម្គាល់បានទេ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"លំនាំដើម"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ពពុះ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"អាណាឡូក"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index 80a98e6ff6d7..c279ceac244e 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪ್ಯಾಟರ್ನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಿನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಾಸ್‌ವರ್ಡ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗೆ ಪ್ಯಾಟರ್ನ್ ಅಗತ್ಯವಿದೆ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗೆ ಪಿನ್ ಅಗತ್ಯವಿದೆ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗಾಗಿ ಪಾಸ್‌ವರ್ಡ್ ಅಗತ್ಯವಿದೆ"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ನಿರ್ವಾಹಕರು ಸಾಧನವನ್ನು ಲಾಕ್ ಮಾಡಿದ್ದಾರೆ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ಸಾಧನವನ್ನು ಹಸ್ತಚಾಲಿತವಾಗಿ ಲಾಕ್‌ ಮಾಡಲಾಗಿದೆ"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ಡೀಫಾಲ್ಟ್"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ಬಬಲ್"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ಅನಲಾಗ್"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index b952f1bba2dd..4c058edbf688 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"기기가 다시 시작되면 패턴이 필요합니다."</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"기기가 다시 시작되면 PIN이 필요합니다."</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"기기가 다시 시작되면 비밀번호가 필요합니다."</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"보안 강화를 위해 패턴이 필요합니다."</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"보안 강화를 위해 PIN이 필요합니다."</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"보안 강화를 위해 비밀번호가 필요합니다."</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"관리자가 기기를 잠갔습니다."</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"기기가 수동으로 잠금 설정되었습니다."</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"인식할 수 없음"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"기본"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"버블"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"아날로그"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 485337d86408..7c7099e1cf61 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Түзмөк кайра күйгүзүлгөндөн кийин графикалык ачкычты тартуу талап кылынат"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Түзмөк кайра күйгүзүлгөндөн кийин PIN-кодду киргизүү талап кылынат"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Түзмөк кайра күйгүзүлгөндөн кийин сырсөздү киргизүү талап кылынат"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Коопсуздукту бекемдөө үчүн графикалык ачкыч талап кылынат"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Коопсуздукту бекемдөө үчүн PIN-код талап кылынат"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Коопсуздукту бекемдөө үчүн сырсөз талап кылынат"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Түзмөктү администратор кулпулап койгон"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Түзмөк кол менен кулпуланды"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Таанылган жок"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Демейки"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Көбүк"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналог"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-lo/strings.xml b/packages/SystemUI/res-keyguard/values-lo/strings.xml
index 17584b5a74fe..5a6df42884b4 100644
--- a/packages/SystemUI/res-keyguard/values-lo/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lo/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ຈຳເປັນຕ້ອງມີແບບຮູບປົດລັອກຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ຈຳເປັນຕ້ອງມີ PIN ຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ຈຳເປັນຕ້ອງມີລະຫັດຜ່ານຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ຈຳເປັນຕ້ອງມີແບບຮູບເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ຈຳເປັນຕ້ອງມີ PIN ເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ຈຳເປັນຕ້ອງມີລະຫັດຜ່ານເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ອຸປະກອນຖືກລັອກໂດຍຜູ້ເບິ່ງແຍງລະບົບ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ອຸປະກອນຖືກສັ່ງໃຫ້ລັອກ"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ບໍ່ຮູ້ຈັກ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ຄ່າເລີ່ມຕົ້ນ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ຟອງ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ໂມງເຂັມ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml
index a066a66ec6a4..4d98fd17baf8 100644
--- a/packages/SystemUI/res-keyguard/values-lt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iš naujo paleidus įrenginį būtinas atrakinimo piešinys"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iš naujo paleidus įrenginį būtinas PIN kodas"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iš naujo paleidus įrenginį būtinas slaptažodis"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Norint užtikrinti papildomą saugą būtinas atrakinimo piešinys"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Norint užtikrinti papildomą saugą būtinas PIN kodas"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Norint užtikrinti papildomą saugą būtinas slaptažodis"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Įrenginį užrakino administratorius"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Įrenginys užrakintas neautomatiškai"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Neatpažinta"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Numatytasis"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Debesėlis"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoginis"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml
index d371a4b9cbab..2660a069c949 100644
--- a/packages/SystemUI/res-keyguard/values-lv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pēc ierīces restartēšanas ir jāievada atbloķēšanas kombinācija."</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pēc ierīces restartēšanas ir jāievada PIN kods."</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pēc ierīces restartēšanas ir jāievada parole."</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Papildu drošībai ir jāievada atbloķēšanas kombinācija."</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Papildu drošībai ir jāievada PIN kods."</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Papildu drošībai ir jāievada parole."</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrators bloķēja ierīci."</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Ierīce tika bloķēta manuāli."</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nav atpazīts"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Noklusējums"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Burbuļi"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogais"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-mk/strings.xml b/packages/SystemUI/res-keyguard/values-mk/strings.xml
index ef22564318e8..77e1b50d7a79 100644
--- a/packages/SystemUI/res-keyguard/values-mk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mk/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Потребна е шема по рестартирање на уредот"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Потребен е PIN-код по рестартирање на уредот"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Потребна е лозинка по рестартирање на уредот"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Потребна е шема за дополнителна безбедност"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Потребен е PIN-код за дополнителна безбедност"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Потребна е лозинка за дополнителна безбедност"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"За дополнителна безбедност, користете шема"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"За дополнителна безбедност, користете PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"За дополнителна безбедност, користете лозинка"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Уредот е заклучен од администраторот"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Уредот е заклучен рачно"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Непознат"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Стандарден"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Балонче"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналоген"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Отклучете го уредот за да продолжите"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index 63a542a63e04..e62b4356822d 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പാറ്റേൺ വരയ്‌ക്കേണ്ടതുണ്ട്"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പിൻ നൽകേണ്ടതുണ്ട്"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പാസ്‌വേഡ് നൽകേണ്ടതുണ്ട്"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"അധിക സുരക്ഷയ്ക്ക് പാറ്റേൺ ആവശ്യമാണ്"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"അധിക സുരക്ഷയ്ക്ക് പിൻ ആവശ്യമാണ്"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"അധിക സുരക്ഷയ്ക്ക് പാസ്‌വേഡ് ആവശ്യമാണ്"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ഉപകരണം അഡ്‌മിൻ ലോക്കുചെയ്തു"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ഉപകരണം നേരിട്ട് ലോക്കുചെയ്തു"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"തിരിച്ചറിയുന്നില്ല"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ഡിഫോൾട്ട്"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ബബിൾ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"അനലോഗ്"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-mn/strings.xml b/packages/SystemUI/res-keyguard/values-mn/strings.xml
index 71c913f988bd..f2cc5ab195a0 100644
--- a/packages/SystemUI/res-keyguard/values-mn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mn/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Төхөөрөмжийг дахин эхлүүлсний дараа загвар оруулах шаардлагатай"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Төхөөрөмжийг дахин эхлүүлсний дараа ПИН оруулах шаардлагатай"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Төхөөрөмжийг дахин эхлүүлсний дараа нууц үг оруулах шаардлагатай"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Аюулгүй байдлын үүднээс загвар оруулах шаардлагатай"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Аюулгүй байдлын үүднээс ПИН оруулах шаардлагатай"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Аюулгүй байдлын үүднээс нууц үг оруулах шаардлагатай"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Нэмэлт аюулгүй байдлын үүднээс оронд нь хээ ашиглана уу"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Нэмэлт аюулгүй байдлын үүднээс оронд нь ПИН ашиглана уу"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Нэмэлт аюулгүй байдлын үүднээс оронд нь нууц үг ашиглана уу"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Админ төхөөрөмжийг түгжсэн"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Төхөөрөмжийг гараар түгжсэн"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Таньж чадсангүй"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Өгөгдмөл"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Бөмбөлөг"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Aналог"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Үргэлжлүүлэхийн тулд төхөөрөмжийнхөө түгжээг тайлна уу"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml
index 6ac13bdde13a..1454b20d3797 100644
--- a/packages/SystemUI/res-keyguard/values-mr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिव्हाइस रीस्टार्ट झाल्यावर पॅटर्न आवश्यक आहे"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिव्हाइस रीस्टार्ट झाल्यावर पिन आवश्यक आहे"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिव्हाइस रीस्टार्ट झाल्यावर पासवर्ड आवश्यक आहे"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"अतिरिक्त सुरक्षिततेसाठी पॅटर्न आवश्‍यक आहे"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"अतिरिक्त सुरक्षिततेसाठी पिन आवश्‍यक आहे"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"अतिरिक्त सुरक्षिततेसाठी पासवर्ड आवश्‍यक आहे"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"प्रशासकाद्वारे लॉक केलेले डिव्हाइस"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"डिव्हाइस मॅन्युअली लॉक केले होते"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ओळखले नाही"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"डीफॉल्ट"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"अ‍ॅनालॉग"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml
index 453afc3d6b37..a6d1af9368ec 100644
--- a/packages/SystemUI/res-keyguard/values-ms/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Corak diperlukan setelah peranti dimulakan semula"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah peranti dimulakan semula"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kata laluan diperlukan setelah peranti dimulakan semula"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Corak diperlukan untuk keselamatan tambahan"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN diperlukan untuk keselamatan tambahan"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Kata laluan diperlukan untuk keselamatan tambahan"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Peranti dikunci oleh pentadbir"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Peranti telah dikunci secara manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Lalai"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Gelembung"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index 1cc46b16ec3f..5617a1188a40 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပုံစံ လိုအပ်ပါသည်"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပင်နံပါတ် လိုအပ်ပါသည်"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် စကားဝှက် လိုအပ်ပါသည်"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ပိုမို၍ လုံခြုံမှု ရှိစေရန် ပုံစံ လိုအပ်ပါသည်"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ပိုမို၍ လုံခြုံမှု ရှိစေရန် ပင်နံပါတ် လိုအပ်ပါသည်"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ပိုမို၍ လုံခြုံမှု ရှိစေရန် စကားဝှက် လိုအပ်ပါသည်"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"စက်ပစ္စည်းကို စီမံခန့်ခွဲသူက လော့ခ်ချထားပါသည်"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"စက်ပစ္စည်းကို ကိုယ်တိုင်ကိုယ်ကျ လော့ခ်ချထားခဲ့သည်"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"မသိ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"မူလ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ပူဖောင်းကွက်"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ရိုးရိုး"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml
index 5310a7301d4e..0ad9e951b1e4 100644
--- a/packages/SystemUI/res-keyguard/values-nb/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du må tegne mønsteret etter at enheten har startet på nytt"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du må skrive inn PIN-koden etter at enheten har startet på nytt"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du må skrive inn passordet etter at enheten har startet på nytt"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Du må tegne mønsteret for ekstra sikkerhet"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Du må skrive inn PIN-koden for ekstra sikkerhet"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Du må skrive inn passordet for ekstra sikkerhet"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Enheten er låst av administratoren"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheten ble låst manuelt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ikke gjenkjent"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Boble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index 534164b3f2c9..196b74a5658b 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"यन्त्र पुनः सुरु भएपछि ढाँचा आवश्यक पर्दछ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"यन्त्र पुनः सुरु भएपछि PIN आवश्यक पर्दछ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"यन्त्र पुनः सुरु भएपछि पासवर्ड आवश्यक पर्दछ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"अतिरिक्त सुरक्षाको लागि ढाँचा आवश्यक छ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"अतिरिक्त सुरक्षाको लागि PIN आवश्यक छ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"अतिरिक्त सुरक्षाको लागि पासवर्ड आवश्यक छ"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"प्रशासकले यन्त्रलाई लक गर्नुभएको छ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"यन्त्रलाई म्यानुअल तरिकाले लक गरिएको थियो"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"पहिचान भएन"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"डिफल्ट"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"एनालग"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index 08e226d4ec07..747b3bbd9128 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon vereist nadat het apparaat opnieuw is opgestart"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pincode vereist nadat het apparaat opnieuw is opgestart"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wachtwoord vereist nadat het apparaat opnieuw is opgestart"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Patroon vereist voor extra beveiliging"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Pincode vereist voor extra beveiliging"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Wachtwoord vereist voor extra beveiliging"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Apparaat vergrendeld door beheerder"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Apparaat is handmatig vergrendeld"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Niet herkend"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standaard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bel"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-or/strings.xml b/packages/SystemUI/res-keyguard/values-or/strings.xml
index 3cdd2649d1b4..75f7a898585b 100644
--- a/packages/SystemUI/res-keyguard/values-or/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-or/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ଡିଭାଇସ୍‍ ରିଷ୍ଟାର୍ଟ ହେବା ପରେ ପାଟର୍ନ ଆବଶ୍ୟକ ଅଟେ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ଡିଭାଇସ୍‍ ରିଷ୍ଟାର୍ଟ ହେବାପରେ ପାସ୍‌ୱର୍ଡ ଆବଶ୍ୟକ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ଡିଭାଇସ୍‍ ରିଷ୍ଟାର୍ଟ ହେବା ପରେ ପାସୱର୍ଡ ଆବଶ୍ୟକ ଅଟେ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ ପାଟର୍ନ ଆବଶ୍ୟକ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ PIN ଆବଶ୍ୟକ ଅଟେ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ ପାସ୍‌ୱର୍ଡ ଆବଶ୍ୟକ"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ, ଏହା ପରିବର୍ତ୍ତେ ପାଟର୍ନ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ, ଏହା ପରିବର୍ତ୍ତେ PIN ବ୍ୟବହାର କରନ୍ତୁ"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ, ଏହା ପରିବର୍ତ୍ତେ ପାସୱାର୍ଡ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ଡିଭାଇସ୍‍ ଆଡମିନଙ୍କ ଦ୍ୱାରା ଲକ୍‍ କରାଯାଇଛି"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ଡିଭାଇସ୍‍ ମାନୁଆଲ ଭାବେ ଲକ୍‍ କରାଗଲା"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ଚିହ୍ନଟ ହେଲାନାହିଁ"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"ଡିଫଲ୍ଟ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ବବଲ୍"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ଆନାଲଗ୍"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ଜାରି ରଖିବା ପାଇଁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ଅନଲକ କରନ୍ତୁ"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index 409f72740649..bf1a359a2c75 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪੈਟਰਨ ਦੀ ਲੋੜ ਹੈ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਿੰਨ ਦੀ ਲੋੜ ਹੈ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੈ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ ਪੈਟਰਨ ਦੀ ਲੋੜ ਹੈ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ ਪਿੰਨ ਦੀ ਲੋੜ ਹੈ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੈ"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਡੀਵਾਈਸ ਨੂੰ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ਡੀਵਾਈਸ ਨੂੰ ਹੱਥੀਂ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ਬੁਲਬੁਲਾ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ਐਨਾਲੌਗ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml
index 52bc98236330..c49149baa93a 100644
--- a/packages/SystemUI/res-keyguard/values-pl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po ponownym uruchomieniu urządzenia wymagany jest wzór"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po ponownym uruchomieniu urządzenia wymagany jest kod PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po ponownym uruchomieniu urządzenia wymagane jest hasło"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Dla większego bezpieczeństwa musisz narysować wzór"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Dla większego bezpieczeństwa musisz podać kod PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Dla większego bezpieczeństwa musisz podać hasło"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Urządzenie zablokowane przez administratora"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Urządzenie zostało zablokowane ręcznie"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nie rozpoznano"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Domyślna"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bąbelkowy"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogowy"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
index b9348260d40e..3d60e8c45bcb 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"O padrão é exigido após a reinicialização do dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"O PIN é exigido após a reinicialização do dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"A senha é exigida após a reinicialização do dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"O padrão é necessário para aumentar a segurança"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"O PIN é necessário para aumentar a segurança"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"A senha é necessária para aumentar a segurança"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para ter mais segurança, use o padrão"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para ter mais segurança, use o PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para ter mais segurança, use a senha"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado pelo administrador"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo foi bloqueado manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Não reconhecido"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Padrão"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bolha"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueie o dispositivo para continuar"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index a67bfb020a3c..0a943496fba9 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"É necessário um padrão após reiniciar o dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"É necessário um PIN após reiniciar o dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"É necessária uma palavra-passe após reiniciar o dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Para segurança adicional, é necessário um padrão"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Para segurança adicional, é necessário um PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Para segurança adicional, é necessária uma palavra-passe"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para uma segurança adicional, use antes o padrão"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para uma segurança adicional, use antes o PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para uma segurança adicional, use antes a palavra-passe"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado pelo gestor"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo foi bloqueado manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Não reconhecido."</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predefinido"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Balão"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueie o dispositivo para continuar"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pt/strings.xml b/packages/SystemUI/res-keyguard/values-pt/strings.xml
index b9348260d40e..3d60e8c45bcb 100644
--- a/packages/SystemUI/res-keyguard/values-pt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"O padrão é exigido após a reinicialização do dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"O PIN é exigido após a reinicialização do dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"A senha é exigida após a reinicialização do dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"O padrão é necessário para aumentar a segurança"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"O PIN é necessário para aumentar a segurança"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"A senha é necessária para aumentar a segurança"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para ter mais segurança, use o padrão"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para ter mais segurança, use o PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para ter mais segurança, use a senha"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado pelo administrador"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo foi bloqueado manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Não reconhecido"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Padrão"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bolha"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueie o dispositivo para continuar"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index 5ee67d917768..547224e781b1 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Modelul este necesar după repornirea dispozitivului"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Codul PIN este necesar după repornirea dispozitivului"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Parola este necesară după repornirea dispozitivului"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Modelul este necesar pentru securitate suplimentară"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Codul PIN este necesar pentru securitate suplimentară"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Parola este necesară pentru securitate suplimentară"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispozitiv blocat de administrator"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Dispozitivul a fost blocat manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nu este recunoscut"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Prestabilit"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogic"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index 2b8f8d6a93f8..f1945ad3fb03 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"После перезагрузки устройства необходимо ввести графический ключ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"После перезагрузки устройства необходимо ввести PIN-код"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"После перезагрузки устройства необходимо ввести пароль"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"В качестве дополнительной меры безопасности введите графический ключ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"В качестве дополнительной меры безопасности введите PIN-код"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"В качестве дополнительной меры безопасности введите пароль"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"В целях дополнительной безопасности используйте графический ключ"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"В целях дополнительной безопасности используйте PIN-код"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"В целях дополнительной безопасности используйте пароль"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Устройство заблокировано администратором"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Устройство было заблокировано вручную"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Не распознано"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"По умолчанию"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Пузырь"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Стрелки"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Чтобы продолжить, разблокируйте устройство"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml
index 4e911defffb6..e5862c358002 100644
--- a/packages/SystemUI/res-keyguard/values-si/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-si/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"උපාංගය නැවත ආරම්භ වූ පසු රටාව අවශ්‍යයි"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"උපාංගය නැවත ආරම්භ වූ පසු PIN අංකය අවශ්‍යයි"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"උපාංගය නැවත ආරම්භ වූ පසු මුරපදය අවශ්‍යයි"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"අමතර ආරක්ෂාව සඳහා රටාව අවශ්‍යයි"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"අමතර ආරක්ෂාව සඳහා PIN අංකය අවශ්‍යයි"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"අමතර ආරක්ෂාව සඳහා මුරපදය අවශ්‍යයි"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ඔබගේ පරිපාලක විසින් උපාංගය අගුළු දමා ඇත"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"උපාංගය හස්තීයව අගුලු දමන ලදී"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"හඳුනා නොගන්නා ලදී"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"පෙරනිමි"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"බුබුළ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ප්‍රතිසමය"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml
index f2d68e3763d3..efe4ec864448 100644
--- a/packages/SystemUI/res-keyguard/values-sk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po reštartovaní zariadenia musíte zadať bezpečnostný vzor"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po reštartovaní zariadenia musíte zadať kód PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po reštartovaní zariadenia musíte zadať heslo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Na ďalšie zabezpečenie musíte zadať bezpečnostný vzor"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Na ďalšie zabezpečenie musíte zadať kód PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Na ďalšie zabezpečenie musíte zadať heslo"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Zariadenie zamkol správca"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Zariadenie bolo uzamknuté ručne"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nerozpoznané"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predvolený"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógový"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml
index 772308f9f658..52726c225498 100644
--- a/packages/SystemUI/res-keyguard/values-sl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po vnovičnem zagonu naprave je treba vnesti vzorec"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po vnovičnem zagonu naprave je treba vnesti kodo PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po vnovičnem zagonu naprave je treba vnesti geslo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Zaradi dodatne varnosti morate vnesti vzorec"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Zaradi dodatne varnosti morate vnesti kodo PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Zaradi dodatne varnosti morate vnesti geslo"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Napravo je zaklenil skrbnik"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Naprava je bila ročno zaklenjena"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ni prepoznano"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Privzeto"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mehurček"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogno"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index c7584622823c..a0a55944eced 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kërkohet motivi pas rinisjes së pajisjes"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Kërkohet kodi PIN pas rinisjes së pajisjes"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kërkohet fjalëkalimi pas rinisjes së pajisjes"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kërkohet motivi për më shumë siguri"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Kërkohet kodi PIN për më shumë siguri"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Kërkohet fjalëkalimi për më shumë siguri"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Pajisja është e kyçur nga administratori"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Pajisja është kyçur manualisht"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nuk njihet"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"E parazgjedhur"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Flluskë"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoge"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index e6fe8531ff4c..e634fdcb586e 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Треба да унесете шаблон када се уређај поново покрене"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Треба да унесете PIN када се уређај поново покрене"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Треба да унесете лозинку када се уређај поново покрене"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Треба да унесете шаблон ради додатне безбедности"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Треба да унесете PIN ради додатне безбедности"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Треба да унесете лозинку ради додатне безбедности"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Администратор је закључао уређај"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Уређај је ручно закључан"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Није препознат"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Подразумевани"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Мехурићи"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналогни"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml
index fa241d96cfae..fc9beb1286a6 100644
--- a/packages/SystemUI/res-keyguard/values-sv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du måste rita mönster när du har startat om enheten"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du måste ange pinkod när du har startat om enheten"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du måste ange lösenord när du har startat om enheten"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Du måste rita mönster för ytterligare säkerhet"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Du måste ange pinkod för ytterligare säkerhet"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Du måste ange lösenord för ytterligare säkerhet"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratören har låst enheten"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheten har låsts manuellt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Identifierades inte"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubbla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml
index 791bceb071e9..bcab24b013bb 100644
--- a/packages/SystemUI/res-keyguard/values-sw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Unafaa kuchora mchoro baada ya kuwasha kifaa upya"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Unafaa kuweka PIN baada ya kuwasha kifaa upya"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Unafaa kuweka nenosiri baada ya kuwasha kifaa upya"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Mchoro unahitajika ili kuongeza usalama"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN inahitajika ili kuongeza usalama"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Nenosiri linahitajika ili kuongeza usalama."</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Msimamizi amefunga kifaa"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Umefunga kifaa mwenyewe"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Haitambuliwi"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Chaguomsingi"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Kiputo"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogi"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 271657d9f0a1..88d5760e7f6c 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"சாதனத்தை மீண்டும் தொடங்கியதும், பேட்டர்னை வரைய வேண்டும்"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"சாதனத்தை மீண்டும் தொடங்கியதும், பின்னை உள்ளிட வேண்டும்"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"சாதனத்தை மீண்டும் தொடங்கியதும், கடவுச்சொல்லை உள்ளிட வேண்டும்"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"கூடுதல் பாதுகாப்பிற்கு, பேட்டர்னை வரைய வேண்டும்"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"கூடுதல் பாதுகாப்பிற்கு, பின்னை உள்ளிட வேண்டும்"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"கூடுதல் பாதுகாப்பிற்கு, கடவுச்சொல்லை உள்ளிட வேண்டும்"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"நிர்வாகி சாதனத்தைப் பூட்டியுள்ளார்"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"பயனர் சாதனத்தைப் பூட்டியுள்ளார்"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"அடையாளங்காணபடவில்லை"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"இயல்பு"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"பபிள்"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"அனலாக்"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index f62e667ee26d..3a0111a193bd 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత నమూనాను గీయాలి"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"డివైజ్‌ను పునఃప్రారంభించిన తర్వాత పిన్ నమోదు చేయాలి"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత పాస్‌వర్డ్‌ను నమోదు చేయాలి"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"అదనపు సెక్యూరిటీ కోసం ఆకృతి అవసరం"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"అదనపు సెక్యూరిటీ కోసం పిన్ ఎంటర్ చేయాలి"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"అదనపు సెక్యూరిటీ కోసం పాస్‌వర్డ్‌ను ఎంటర్ చేయాలి"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"పరికరం నిర్వాహకుల ద్వారా లాక్ చేయబడింది"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"పరికరం మాన్యువల్‌గా లాక్ చేయబడింది"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"గుర్తించలేదు"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ఆటోమేటిక్"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"బబుల్"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ఎనలాగ్"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-th/strings.xml b/packages/SystemUI/res-keyguard/values-th/strings.xml
index 62a83bcf9d7a..14a65a074f87 100644
--- a/packages/SystemUI/res-keyguard/values-th/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-th/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ต้องวาดรูปแบบหลังจากอุปกรณ์รีสตาร์ท"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ต้องระบุ PIN หลังจากอุปกรณ์รีสตาร์ท"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ต้องป้อนรหัสผ่านหลังจากอุปกรณ์รีสตาร์ท"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ต้องวาดรูปแบบเพื่อความปลอดภัยเพิ่มเติม"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ต้องระบุ PIN เพื่อความปลอดภัยเพิ่มเติม"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ต้องป้อนรหัสผ่านเพื่อความปลอดภัยเพิ่มเติม"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ใช้รูปแบบแทนเพื่อเพิ่มความปลอดภัย"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ใช้ PIN แทนเพื่อเพิ่มความปลอดภัย"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ใช้รหัสผ่านแทนเพื่อเพิ่มความปลอดภัย"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ผู้ดูแลระบบล็อกอุปกรณ์"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"มีการล็อกอุปกรณ์ด้วยตัวเอง"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ไม่รู้จัก"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"ค่าเริ่มต้น"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"บับเบิล"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"แอนะล็อก"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ปลดล็อกอุปกรณ์ของคุณเพื่อดำเนินการต่อ"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-tl/strings.xml b/packages/SystemUI/res-keyguard/values-tl/strings.xml
index 524ea4782506..7936058581ae 100644
--- a/packages/SystemUI/res-keyguard/values-tl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tl/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kailangan ng pattern pagkatapos mag-restart ng device"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Kailangan ng PIN pagkatapos mag-restart ng device"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kailangan ng password pagkatapos mag-restart ng device"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kinakailangan ang pattern para sa karagdagang seguridad"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Kinakailangan ang PIN para sa karagdagang seguridad"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Kinakailangan ang password para sa karagdagang seguridad"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para sa karagdagang seguridad, gumamit na lang ng pattern"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para sa karagdagang seguridad, gumamit na lang ng PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para sa karagdagang seguridad, gumamit na lang ng password"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Na-lock ng admin ang device"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Manual na na-lock ang device"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Hindi nakilala"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"I-unlock ang iyong device para magpatuloy"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml
index 54aaae38b18c..e5207623adc2 100644
--- a/packages/SystemUI/res-keyguard/values-tr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yeniden başladıktan sonra desen gerekir"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıktan sonra PIN gerekir"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıktan sonra şifre gerekir"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Ek güvenlik için desen gerekir"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Ek güvenlik için PIN gerekir"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Ek güvenlik için şifre gerekir"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Cihaz, yönetici tarafından kilitlendi"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Cihazın manuel olarak kilitlendi"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tanınmadı"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Varsayılan"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Baloncuk"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml
index 6144c1c4e0d5..613181d6c96f 100644
--- a/packages/SystemUI/res-keyguard/values-uk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Після перезавантаження пристрою потрібно ввести ключ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Після перезавантаження пристрою потрібно ввести PIN-код"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Після перезавантаження пристрою потрібно ввести пароль"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Для додаткового захисту потрібно ввести ключ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Для додаткового захисту потрібно ввести PIN-код"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Для додаткового захисту потрібно ввести пароль"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Адміністратор заблокував пристрій"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Пристрій заблоковано вручну"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Не розпізнано"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"За умовчанням"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Бульбашковий"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналоговий"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index 4e778413775e..a122f8537611 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"آلہ دوبارہ چالو ہونے کے بعد پیٹرن درکار ہوتا ہے"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"‏آلہ دوبارہ چالو ہونے کے بعد PIN درکار ہوتا ہے"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"آلہ دوبارہ چالو ہونے کے بعد پاس ورڈ درکار ہوتا ہے"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"اضافی سیکیورٹی کیلئے پیٹرن درکار ہے"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"‏اضافی سیکیورٹی کیلئے PIN درکار ہے"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"اضافی سیکیورٹی کیلئے پاس ورڈ درکار ہے"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"آلہ منتظم کی جانب سے مقفل ہے"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"آلہ کو دستی طور پر مقفل کیا گیا تھا"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"تسلیم شدہ نہیں ہے"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ڈیفالٹ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"بلبلہ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"اینالاگ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index afaf7464d091..2cc9724dc53b 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Qurilma qayta ishga tushganidan keyin grafik kalitni kiritish zarur"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Qurilma qayta ishga tushganidan keyin PIN kodni kiritish zarur"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Qurilma qayta ishga tushganidan keyin parolni kiritish zarur"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Qo‘shimcha xavfsizlik chorasi sifatida grafik kalit talab qilinadi"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Qo‘shimcha xavfsizlik chorasi sifatida PIN kod talab qilinadi"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Qo‘shimcha xavfsizlik chorasi sifatida parol talab qilinadi"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Qoʻshimcha xavfsizlik maqsadida oʻrniga grafik kalitdan foydalaning"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Qoʻshimcha xavfsizlik maqsadida oʻrniga PIN koddan foydalaning"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Qoʻshimcha xavfsizlik maqsadida oʻrniga paroldan foydalaning"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Qurilma administrator tomonidan bloklangan"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Qurilma qo‘lda qulflangan"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Aniqlanmadi"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Odatiy"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Pufaklar"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Davom etish uchun qurilmangizni qulfdan chiqaring"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml
index 1d6cfa85e4ed..e7c9295815ad 100644
--- a/packages/SystemUI/res-keyguard/values-vi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Yêu cầu hình mở khóa sau khi thiết bị khởi động lại"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Yêu cầu mã PIN sau khi thiết bị khởi động lại"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Yêu cầu mật khẩu sau khi thiết bị khởi động lại"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Yêu cầu hình mở khóa để bảo mật thêm"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Yêu cầu mã PIN để bảo mật thêm"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Yêu cầu mật khẩu để bảo mật thêm"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Thiết bị đã bị quản trị viên khóa"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Thiết bị đã bị khóa theo cách thủ công"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Không nhận dạng được"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Mặc định"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bong bóng"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Đồng hồ kim"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index 8c8507ed06fa..d37d645b15ee 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"重启设备后需要绘制解锁图案"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"重启设备后需要输入 PIN 码"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"重启设备后需要输入密码"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"需要绘制解锁图案以进一步确保安全"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"需要输入 PIN 码以进一步确保安全"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"需要输入密码以进一步确保安全"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"管理员已锁定设备"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"此设备已手动锁定"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"无法识别"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"默认"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"指针"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
index c331a925be39..9dbb8f2dac73 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後,必須畫出上鎖圖案才能使用"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後,必須輸入 PIN 碼才能使用"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後,必須輸入密碼才能使用"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"請務必畫出上鎖圖案,以進一步確保安全"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"請務必輸入 PIN 碼,以進一步確保安全"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"請務必輸入密碼,以進一步確保安全"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"裝置已由管理員鎖定"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"使用者已手動將裝置上鎖"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"未能識別"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"預設"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"指針"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
index 1e1bec3ef76a..ebb88e13194b 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後需要畫出解鎖圖案"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後需要輸入 PIN 碼"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後需要輸入密碼"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"請畫出解鎖圖案,以進一步確保資訊安全"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"請輸入 PIN 碼,以進一步確保資訊安全"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"請輸入密碼,以進一步確保資訊安全"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"管理員已鎖定裝置"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"裝置已手動鎖定"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"無法識別"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"預設"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"類比"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml
index c8f78ea441f5..57e56f713536 100644
--- a/packages/SystemUI/res-keyguard/values-zu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iphethini iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iphinikhodi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iphasiwedi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kudingeka iphethini ngokuvikeleka okungeziwe"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Kudingeka iphinikhodi ngokuvikeleka okungeziwe"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Iphasiwedi idingelwa ukuvikela okungeziwe"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Idivayisi ikhiywe ngumlawuli"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Idivayisi ikhiywe ngokwenza"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Akwaziwa"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Okuzenzekelayo"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Ibhamuza"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"I-Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml
index b1d33758f1b3..a25ab5109fa8 100644
--- a/packages/SystemUI/res-keyguard/values/config.xml
+++ b/packages/SystemUI/res-keyguard/values/config.xml
@@ -28,11 +28,6 @@
<!-- Will display the bouncer on one side of the display, and the current user icon and
user switcher on the other side -->
<bool name="config_enableBouncerUserSwitcher">false</bool>
- <!-- Whether to show the face scanning animation on devices with face auth supported.
- The face scanning animation renders in a SW layer in ScreenDecorations.
- Enabling this will also render the camera protection in the SW layer
- (instead of HW, if relevant)."=-->
- <bool name="config_enableFaceScanningAnimation">true</bool>
<!-- Time to be considered a consecutive fingerprint failure in ms -->
<integer name="fp_consecutive_failure_time_ms">3500</integer>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 46f6ab2399d1..0a55cf779683 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -119,6 +119,7 @@
<dimen name="bouncer_user_switcher_width">248dp</dimen>
<dimen name="bouncer_user_switcher_popup_header_height">12dp</dimen>
<dimen name="bouncer_user_switcher_popup_divider_height">4dp</dimen>
+ <dimen name="bouncer_user_switcher_popup_items_divider_height">2dp</dimen>
<dimen name="bouncer_user_switcher_item_padding_vertical">10dp</dimen>
<dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
<dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 8135aaa6faea..a129fb650ba6 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -201,13 +201,13 @@
<string name="kg_prompt_reason_restart_password">Password required after device restarts</string>
<!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
- <string name="kg_prompt_reason_timeout_pattern">Pattern required for additional security</string>
+ <string name="kg_prompt_reason_timeout_pattern">For additional security, use pattern instead</string>
<!-- An explanation text that the pin needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
- <string name="kg_prompt_reason_timeout_pin">PIN required for additional security</string>
+ <string name="kg_prompt_reason_timeout_pin">For additional security, use PIN instead</string>
<!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
- <string name="kg_prompt_reason_timeout_password">Password required for additional security</string>
+ <string name="kg_prompt_reason_timeout_password">For additional security, use password instead</string>
<!-- An explanation text that the credential needs to be entered because a device admin has
locked the device. [CHAR LIMIT=80] -->
diff --git a/packages/SystemUI/res/drawable-hdpi/textfield_default_filled.9.png b/packages/SystemUI/res/drawable-hdpi/textfield_default_filled.9.png
new file mode 100644
index 000000000000..3dd997fade6c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/textfield_default_filled.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/textfield_default_filled.9.png b/packages/SystemUI/res/drawable-mdpi/textfield_default_filled.9.png
new file mode 100644
index 000000000000..80aba01091fe
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/textfield_default_filled.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/textfield_default_filled.9.png b/packages/SystemUI/res/drawable-xhdpi/textfield_default_filled.9.png
new file mode 100644
index 000000000000..b3f89ed7ea7a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/textfield_default_filled.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/textfield_default_filled.9.png b/packages/SystemUI/res/drawable-xxhdpi/textfield_default_filled.9.png
new file mode 100644
index 000000000000..efa2cb988ac1
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/textfield_default_filled.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/edit_text_filled.xml b/packages/SystemUI/res/drawable/edit_text_filled.xml
new file mode 100644
index 000000000000..cca34d456078
--- /dev/null
+++ b/packages/SystemUI/res/drawable/edit_text_filled.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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="4dp"
+ android:insetRight="4dp"
+ android:insetTop="10dp"
+ android:insetBottom="10dp">
+ <selector>
+ <item android:state_enabled="false">
+ <nine-patch android:src="@drawable/textfield_default_filled"
+ android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:state_pressed="false" android:state_focused="false">
+ <nine-patch android:src="@drawable/textfield_default_filled"
+ android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item>
+ <nine-patch android:src="@drawable/textfield_default_filled"
+ android:tint="?android:attr/colorControlActivated" />
+ </item>
+ </selector>
+</inset>
diff --git a/packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml b/packages/SystemUI/res/drawable/media_squiggly_progress.xml
index 9e61236aa7df..9cd3f6288b1d 100644
--- a/packages/SystemUI/res-keyguard/drawable/media_squiggly_progress.xml
+++ b/packages/SystemUI/res/drawable/media_squiggly_progress.xml
@@ -14,4 +14,4 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.media.SquigglyProgress /> \ No newline at end of file
+<com.android.systemui.media.controls.ui.SquigglyProgress /> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_media_background.xml b/packages/SystemUI/res/drawable/qs_media_background.xml
index 6ed3a0aed091..217656dab022 100644
--- a/packages/SystemUI/res/drawable/qs_media_background.xml
+++ b/packages/SystemUI/res/drawable/qs_media_background.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<com.android.systemui.media.IlluminationDrawable
+<com.android.systemui.media.controls.ui.IlluminationDrawable
xmlns:systemui="http://schemas.android.com/apk/res-auto"
systemui:highlight="15"
systemui:cornerRadius="@dimen/notification_corner_radius" /> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_media_light_source.xml b/packages/SystemUI/res/drawable/qs_media_light_source.xml
index b2647c1f6697..849349a5f100 100644
--- a/packages/SystemUI/res/drawable/qs_media_light_source.xml
+++ b/packages/SystemUI/res/drawable/qs_media_light_source.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.media.LightSourceDrawable
+<com.android.systemui.media.controls.ui.LightSourceDrawable
xmlns:systemui="http://schemas.android.com/apk/res-auto"
systemui:rippleMinSize="25dp"
systemui:rippleMaxSize="135dp" /> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
index da76c8d0b11a..3bcc37a478c9 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
@@ -16,46 +16,74 @@
<com.android.systemui.biometrics.AuthCredentialPasswordView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center_horizontal"
- android:elevation="@dimen/biometric_dialog_elevation">
+ android:orientation="horizontal"
+ android:elevation="@dimen/biometric_dialog_elevation"
+ android:theme="?app:attr/lockPinPasswordStyle">
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Title"/>
+ <RelativeLayout
+ android:id="@+id/auth_credential_header"
+ style="?headerStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Subtitle"/>
+ <ImageView
+ android:id="@+id/icon"
+ style="?headerIconStyle"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:contentDescription="@null"/>
- <TextView
- android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Description"/>
+ <TextView
+ android:id="@+id/title"
+ style="?titleTextAppearance"
+ android:layout_below="@id/icon"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
- <ImeAwareEditText
- android:id="@+id/lockPassword"
- android:layout_width="208dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:minHeight="48dp"
- android:gravity="center"
- android:inputType="textPassword"
- android:maxLength="500"
- android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
- style="@style/TextAppearance.AuthCredential.PasswordEntry"/>
-
- <TextView
- android:id="@+id/error"
- android:layout_width="match_parent"
+ <TextView
+ android:id="@+id/subtitle"
+ style="?subTitleTextAppearance"
+ android:layout_below="@id/title"
+ android:layout_alignParentLeft="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/description"
+ style="?descriptionTextAppearance"
+ android:layout_below="@id/subtitle"
+ android:layout_alignParentLeft="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </RelativeLayout>
+
+ <LinearLayout
+ android:id="@+id/auth_credential_input"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Error"/>
+ android:orientation="vertical">
+
+ <ImeAwareEditText
+ android:id="@+id/lockPassword"
+ style="?passwordTextAppearance"
+ android:layout_width="208dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+ android:inputType="textPassword"
+ android:minHeight="48dp" />
+
+ <TextView
+ android:id="@+id/error"
+ style="?errorTextAppearance"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
</com.android.systemui.biometrics.AuthCredentialPasswordView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
index 19a85fec1397..a3dd334bd667 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
@@ -16,91 +16,71 @@
<com.android.systemui.biometrics.AuthCredentialPatternView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
- android:elevation="@dimen/biometric_dialog_elevation">
+ android:elevation="@dimen/biometric_dialog_elevation"
+ android:theme="?app:attr/lockPatternStyle">
- <LinearLayout
+ <RelativeLayout
+ android:id="@+id/auth_credential_header"
+ style="?headerStyle"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:gravity="center"
- android:orientation="vertical">
-
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ android:layout_weight="1">
<ImageView
android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ style="?headerIconStyle"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:contentDescription="@null"/>
<TextView
android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Title"/>
+ style="?titleTextAppearance"
+ android:layout_below="@id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
<TextView
android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Subtitle"/>
+ style="?subTitleTextAppearance"
+ android:layout_below="@id/title"
+ android:layout_alignParentLeft="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
<TextView
android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Description"/>
+ style="?descriptionTextAppearance"
+ android:layout_below="@id/subtitle"
+ android:layout_alignParentLeft="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ </RelativeLayout>
+
+ <FrameLayout
+ android:layout_weight="1"
+ style="?containerStyle"
+ android:layout_width="0dp"
+ android:layout_height="match_parent">
+
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPattern"
+ android:layout_gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
<TextView
android:id="@+id/error"
+ style="?errorTextAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Error"/>
-
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:orientation="vertical"
- android:gravity="center"
- android:paddingLeft="0dp"
- android:paddingRight="0dp"
- android:paddingTop="0dp"
- android:paddingBottom="16dp"
- android:clipToPadding="false">
-
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1"
- style="@style/LockPatternContainerStyle">
-
- <com.android.internal.widget.LockPatternView
- android:id="@+id/lockPattern"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- style="@style/LockPatternStyleBiometricPrompt"/>
-
- </FrameLayout>
+ android:layout_gravity="center_horizontal|bottom"/>
- </LinearLayout>
+ </FrameLayout>
</com.android.systemui.biometrics.AuthCredentialPatternView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 0ff1db2ef694..774b335f913e 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -16,74 +16,71 @@
<com.android.systemui.biometrics.AuthCredentialPasswordView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="@dimen/biometric_dialog_elevation"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:theme="?app:attr/lockPinPasswordStyle">
<RelativeLayout
+ android:id="@+id/auth_credential_header"
+ style="?headerStyle"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/auth_credential_header"
- style="@style/AuthCredentialHeaderStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true">
+ android:layout_height="match_parent">
- <ImageView
- android:id="@+id/icon"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:contentDescription="@null" />
+ <ImageView
+ android:id="@+id/icon"
+ style="?headerIconStyle"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:contentDescription="@null"/>
- <TextView
- android:id="@+id/title"
- style="@style/TextAppearance.AuthNonBioCredential.Title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <TextView
- android:id="@+id/subtitle"
- style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <TextView
- android:id="@+id/description"
- style="@style/TextAppearance.AuthNonBioCredential.Description"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/title"
+ style="?titleTextAppearance"
+ android:layout_below="@id/icon"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
- </LinearLayout>
+ <TextView
+ android:id="@+id/subtitle"
+ style="?subTitleTextAppearance"
+ android:layout_below="@id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
- <LinearLayout
+ <TextView
+ android:id="@+id/description"
+ style="?descriptionTextAppearance"
+ android:layout_below="@id/subtitle"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:orientation="vertical"
- android:layout_alignParentBottom="true">
+ android:layout_height="wrap_content"/>
+ </RelativeLayout>
- <ImeAwareEditText
- android:id="@+id/lockPassword"
- style="@style/TextAppearance.AuthCredential.PasswordEntry"
- android:layout_width="208dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
- android:inputType="textPassword"
- android:minHeight="48dp" />
+ <LinearLayout
+ android:id="@+id/auth_credential_input"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
- <TextView
- android:id="@+id/error"
- style="@style/TextAppearance.AuthNonBioCredential.Error"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ <ImeAwareEditText
+ android:id="@+id/lockPassword"
+ style="?passwordTextAppearance"
+ android:layout_width="208dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+ android:inputType="textPassword"
+ android:minHeight="48dp" />
- </LinearLayout>
+ <TextView
+ android:id="@+id/error"
+ style="?errorTextAppearance"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
- </RelativeLayout>
+ </LinearLayout>
</com.android.systemui.biometrics.AuthCredentialPasswordView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index dada9813c320..4af997017bba 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -16,87 +16,66 @@
<com.android.systemui.biometrics.AuthCredentialPatternView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:gravity="center_horizontal"
- android:elevation="@dimen/biometric_dialog_elevation">
+ android:elevation="@dimen/biometric_dialog_elevation"
+ android:theme="?app:attr/lockPatternStyle">
<RelativeLayout
+ android:id="@+id/auth_credential_header"
+ style="?headerStyle"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/auth_credential_header"
- style="@style/AuthCredentialHeaderStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:contentDescription="@null" />
-
- <TextView
- android:id="@+id/title"
- style="@style/TextAppearance.AuthNonBioCredential.Title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <TextView
- android:id="@+id/subtitle"
- style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/icon"
+ style="?headerIconStyle"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:contentDescription="@null"/>
+
+ <TextView
+ android:id="@+id/title"
+ style="?titleTextAppearance"
+ android:layout_below="@id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/subtitle"
+ style="?subTitleTextAppearance"
+ android:layout_below="@id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/description"
+ style="?descriptionTextAppearance"
+ android:layout_below="@id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </RelativeLayout>
- <TextView
- android:id="@+id/description"
- style="@style/TextAppearance.AuthNonBioCredential.Description"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- </LinearLayout>
+ <FrameLayout
+ android:id="@+id/auth_credential_container"
+ style="?containerStyle"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
- <LinearLayout
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPattern"
+ android:layout_gravity="center"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/auth_credential_header"
- android:gravity="center"
- android:orientation="vertical"
- android:paddingBottom="16dp"
- android:paddingTop="60dp">
+ android:layout_height="match_parent"/>
- <FrameLayout
- style="@style/LockPatternContainerStyle"
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1">
-
- <com.android.internal.widget.LockPatternView
- android:id="@+id/lockPattern"
- style="@style/LockPatternStyle"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center" />
-
- </FrameLayout>
-
- </LinearLayout>
-
- <LinearLayout
+ <TextView
+ android:id="@+id/error"
+ style="?errorTextAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentBottom="true">
-
- <TextView
- android:id="@+id/error"
- style="@style/TextAppearance.AuthNonBioCredential.Error"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
-
- </LinearLayout>
-
- </RelativeLayout>
+ android:layout_gravity="center_horizontal|bottom"/>
+ </FrameLayout>
</com.android.systemui.biometrics.AuthCredentialPatternView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/chipbar.xml
index ae8e38e2634b..bc97e511e7f4 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -16,15 +16,15 @@
<!-- Wrap in a frame layout so that we can update the margins on the inner layout. (Since this view
is the root view of a window, we cannot change the root view's margins.) -->
<!-- Alphas start as 0 because the view will be animated in. -->
-<com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView
+<com.android.systemui.temporarydisplay.chipbar.ChipbarRootView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:id="@+id/media_ttt_sender_chip"
+ android:id="@+id/chipbar_root_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
- android:id="@+id/media_ttt_sender_chip_inner"
+ android:id="@+id/chipbar_inner"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -39,7 +39,7 @@
>
<com.android.internal.widget.CachingIconView
- android:id="@+id/app_icon"
+ android:id="@+id/start_icon"
android:layout_width="@dimen/media_ttt_app_icon_size"
android:layout_height="@dimen/media_ttt_app_icon_size"
android:layout_marginEnd="12dp"
@@ -69,7 +69,7 @@
/>
<ImageView
- android:id="@+id/failure_icon"
+ android:id="@+id/error"
android:layout_width="@dimen/media_ttt_status_icon_size"
android:layout_height="@dimen/media_ttt_status_icon_size"
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
@@ -78,11 +78,11 @@
android:alpha="0.0"
/>
+ <!-- TODO(b/245610654): Re-name all the media-specific dimens to chipbar dimens instead. -->
<TextView
- android:id="@+id/undo"
+ android:id="@+id/end_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/media_transfer_undo"
android:textColor="?androidprv:attr/textColorOnAccent"
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
android:textSize="@dimen/media_ttt_text_size"
@@ -97,4 +97,4 @@
/>
</LinearLayout>
-</com.android.systemui.media.taptotransfer.sender.MediaTttChipRootView>
+</com.android.systemui.temporarydisplay.chipbar.ChipbarRootView>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 1a1fc75a41a1..0e9abee2f050 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.screenshot.DraggableConstraintLayout
+<com.android.systemui.clipboardoverlay.ClipboardOverlayView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -157,4 +157,4 @@
android:layout_margin="@dimen/overlay_dismiss_button_margin"
android:src="@drawable/overlay_cancel"/>
</FrameLayout>
-</com.android.systemui.screenshot.DraggableConstraintLayout> \ No newline at end of file
+</com.android.systemui.clipboardoverlay.ClipboardOverlayView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml
new file mode 100644
index 000000000000..1a1fc75a41a1
--- /dev/null
+++ b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.systemui.screenshot.DraggableConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/clipboard_ui"
+ android:theme="@style/FloatingOverlay"
+ android:alpha="0"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:contentDescription="@string/clipboard_overlay_window_name">
+ <ImageView
+ android:id="@+id/actions_container_background"
+ android:visibility="gone"
+ android:layout_height="0dp"
+ android:layout_width="0dp"
+ android:elevation="4dp"
+ android:background="@drawable/action_chip_container_background"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ app:layout_constraintBottom_toBottomOf="@+id/actions_container"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/actions_container"
+ app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
+ <HorizontalScrollView
+ android:id="@+id/actions_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+ android:paddingEnd="@dimen/overlay_action_container_padding_right"
+ android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
+ android:elevation="4dp"
+ android:scrollbars="none"
+ android:layout_marginBottom="4dp"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintWidth_percent="1.0"
+ app:layout_constraintWidth_max="wrap"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/preview_border"
+ app:layout_constraintEnd_toEndOf="parent">
+ <LinearLayout
+ android:id="@+id/actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:animateLayoutChanges="true">
+ <include layout="@layout/overlay_action_chip"
+ android:id="@+id/share_chip"/>
+ <include layout="@layout/overlay_action_chip"
+ android:id="@+id/remote_copy_chip"/>
+ <include layout="@layout/overlay_action_chip"
+ android:id="@+id/edit_chip"/>
+ </LinearLayout>
+ </HorizontalScrollView>
+ <View
+ android:id="@+id/preview_border"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="@dimen/overlay_offset_x"
+ android:layout_marginBottom="12dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:elevation="7dp"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
+ app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
+ android:background="@drawable/overlay_border"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_preview_end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierMargin="@dimen/overlay_border_width"
+ app:barrierDirection="end"
+ app:constraint_referenced_ids="clipboard_preview"/>
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/clipboard_preview_top"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierDirection="top"
+ app:barrierMargin="@dimen/overlay_border_width_neg"
+ app:constraint_referenced_ids="clipboard_preview"/>
+ <FrameLayout
+ android:id="@+id/clipboard_preview"
+ android:elevation="7dp"
+ android:background="@drawable/overlay_preview_background"
+ android:clipChildren="true"
+ android:clipToOutline="true"
+ android:clipToPadding="true"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_margin="@dimen/overlay_border_width"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ app:layout_constraintBottom_toBottomOf="@id/preview_border"
+ app:layout_constraintStart_toStartOf="@id/preview_border"
+ app:layout_constraintEnd_toEndOf="@id/preview_border"
+ app:layout_constraintTop_toTopOf="@id/preview_border">
+ <TextView android:id="@+id/text_preview"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center|start"
+ android:ellipsize="end"
+ android:autoSizeTextType="uniform"
+ android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
+ android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
+ android:textColor="?attr/overlayButtonTextColor"
+ android:textColorLink="?attr/overlayButtonTextColor"
+ android:background="?androidprv:attr/colorAccentSecondary"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
+ <ImageView
+ android:id="@+id/image_preview"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true"
+ android:contentDescription="@string/clipboard_image_preview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/hidden_preview"
+ android:visibility="gone"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center"
+ android:textSize="14sp"
+ android:textColor="?attr/overlayButtonTextColor"
+ android:background="?androidprv:attr/colorAccentSecondary"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
+ </FrameLayout>
+ <FrameLayout
+ android:id="@+id/dismiss_button"
+ android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+ android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+ android:elevation="10dp"
+ android:visibility="gone"
+ android:alpha="0"
+ app:layout_constraintStart_toEndOf="@id/clipboard_preview"
+ app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
+ app:layout_constraintTop_toTopOf="@id/clipboard_preview"
+ app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+ android:contentDescription="@string/clipboard_dismiss_description">
+ <ImageView
+ android:id="@+id/dismiss_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/overlay_dismiss_button_margin"
+ android:src="@drawable/overlay_cancel"/>
+ </FrameLayout>
+</com.android.systemui.screenshot.DraggableConstraintLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 5dc34b9db594..a565988c14ad 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -73,8 +73,8 @@
android:singleLine="true"
android:textDirection="locale"
android:textAppearance="@style/TextAppearance.QS.Status"
- android:transformPivotX="0sp"
- android:transformPivotY="20sp"
+ android:transformPivotX="0dp"
+ android:transformPivotY="24dp"
android:scaleX="1"
android:scaleY="1"
/>
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 50d3cc4e8a7a..715c86957e02 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -24,7 +24,7 @@
android:clipToPadding="false"
android:forceHasOverlappingRendering="false"
android:theme="@style/MediaPlayer">
- <com.android.systemui.media.MediaScrollView
+ <com.android.systemui.media.controls.ui.MediaScrollView
android:id="@+id/media_carousel_scroller"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -42,7 +42,7 @@
>
<!-- QSMediaPlayers will be added here dynamically -->
</LinearLayout>
- </com.android.systemui.media.MediaScrollView>
+ </com.android.systemui.media.controls.ui.MediaScrollView>
<com.android.systemui.qs.PageIndicator
android:id="@+id/media_page_indicator"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index f0e49d5c2011..92ef3f8201cb 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -32,41 +32,8 @@
android:layout_height="match_parent"
android:layout_width="match_parent" />
- <include
- layout="@layout/keyguard_bottom_area"
- android:visibility="gone" />
-
- <ViewStub
- android:id="@+id/keyguard_user_switcher_stub"
- android:layout="@layout/keyguard_user_switcher"
- android:layout_height="match_parent"
- android:layout_width="match_parent" />
-
<include layout="@layout/status_bar_expanded_plugin_frame"/>
- <include layout="@layout/dock_info_bottom_area_overlay" />
-
- <com.android.keyguard.LockIconView
- android:id="@+id/lock_icon_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <!-- Background protection -->
- <ImageView
- android:id="@+id/lock_icon_bg"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/fingerprint_bg"
- android:visibility="invisible"/>
-
- <ImageView
- android:id="@+id/lock_icon"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:scaleType="centerCrop"/>
-
- </com.android.keyguard.LockIconView>
-
<com.android.systemui.shade.NotificationsQuickSettingsContainer
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -145,6 +112,39 @@
/>
</com.android.systemui.shade.NotificationsQuickSettingsContainer>
+ <include
+ layout="@layout/keyguard_bottom_area"
+ android:visibility="gone" />
+
+ <ViewStub
+ android:id="@+id/keyguard_user_switcher_stub"
+ android:layout="@layout/keyguard_user_switcher"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent" />
+
+ <include layout="@layout/dock_info_bottom_area_overlay" />
+
+ <com.android.keyguard.LockIconView
+ android:id="@+id/lock_icon_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <!-- Background protection -->
+ <ImageView
+ android:id="@+id/lock_icon_bg"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/fingerprint_bg"
+ android:visibility="invisible"/>
+
+ <ImageView
+ android:id="@+id/lock_icon"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:scaleType="centerCrop"/>
+
+ </com.android.keyguard.LockIconView>
+
<FrameLayout
android:id="@+id/preview_container"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index f07f10d47476..eccce2f79844 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -94,19 +94,14 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ለአንድ የማያ ገጽ ቀረጻ ክፍለ-ጊዜ በመካሄድ ያለ ማሳወቂያ"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"መቅረጽ ይጀመር?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"እየቀረጹ ሳለ የAndroid ስርዓት በማያ ገጽዎ ላይ የሚታይ ወይም በመሣሪያዎ ላይ የሚጫወት ማንኛውም ሚስጥራዊነት ያለው መረጃን መያዝ ይችላል። ይህ የይለፍ ቃላትን፣ የክፍያ መረጃን፣ ፎቶዎችን፣ መልዕክቶችን እና ኦዲዮን ያካትታል።"</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"መላው ማያ ገጹን ቅረጽ"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"አንድ ነጠላ መተግበሪያን ቅረጽ"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"እየቀረጹ እያለ Android በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"አንድን መተግበሪያ እየቀረጹ ሳለ Android በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"መቅረጽ ጀምር"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ኦዲዮን ቅረጽ"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"የመሣሪያ ኦዲዮ"</string>
- <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"እንደ ሙዚቃ፣ ጥሪዎች እና የጥሪ ቅላጼዎች ያሉ የመሣሪያዎ ድምጽ"</string>
+ <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"እንደ ሙዚቃ፣ ጥሪዎች እና የጥሪ ቅላጼዎች ያሉ የመሣሪያዎ ድምፅ"</string>
<string name="screenrecord_mic_label" msgid="2111264835791332350">"ማይክሮፎን"</string>
<string name="screenrecord_device_audio_and_mic_label" msgid="1831323771978646841">"የመሣሪያ ኦዲዮ እና ማይክሮፎን"</string>
<string name="screenrecord_start" msgid="330991441575775004">"ጀምር"</string>
@@ -274,14 +269,14 @@
<string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"የ<xliff:g id="DATA_LIMIT">%s</xliff:g> ማስጠንቀቂያ"</string>
<string name="quick_settings_work_mode_label" msgid="6440531507319809121">"የሥራ መተግበሪያዎች"</string>
<string name="quick_settings_night_display_label" msgid="8180030659141778180">"የምሽት ብርሃን"</string>
- <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"ጸሐይ ስትጠልቅ ይበራል"</string>
- <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"ጸሐይ እስክትወጣ ድረስ"</string>
+ <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"ፀሐይ ስትጠልቅ ይበራል"</string>
+ <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"ፀሐይ እስክትወጣ ድረስ"</string>
<string name="quick_settings_night_secondary_label_on_at" msgid="3584738542293528235">"<xliff:g id="TIME">%s</xliff:g> ላይ ይበራል"</string>
<string name="quick_settings_secondary_label_until" msgid="1883981263191927372">"እስከ <xliff:g id="TIME">%s</xliff:g> ድረስ"</string>
<string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"ጨለማ ገጽታ"</string>
<string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ባትሪ ቆጣቢ"</string>
- <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ጸሐይ ስትጠልቅ ይበራል"</string>
- <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ጸሐይ እስክትወጣ ድረስ"</string>
+ <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ፀሐይ ስትጠልቅ ይበራል"</string>
+ <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ፀሐይ እስክትወጣ ድረስ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> ላይ ይበራል"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"እስከ <xliff:g id="TIME">%s</xliff:g> ድረስ"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"በመኝታ ሰዓት ላይ"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ይህን ተግባር የሚያቀርበው አገልግሎት በእርስዎ ማያ ገጽ ላይ ያለን ወይም በእርስዎ መሣሪያ ላይ በመጫወት ላይ ያለን ሁሉንም መረጃ በቀረጻ ወይም casting ላይ እያለ መዳረሻ ይኖረዋል። ይህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ ፎቶዎች፣ መልዕክቶች እና እርስዎ የሚጫውቱት ኦዲዮን የመሳሰለ መረጃን ያካትታል።"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ቀረጻ ወይም cast ማድረግ ይጀምር?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"ከ<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ጋር ቀረጻ ወይም casting ይጀምር?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> እንዲያጋራ ወይም እንዲቀርጽ ይፈቀድለት?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"መላው ማያ ገጽ"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"አንድ ነጠላ መተግበሪያ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"አንድን መተግበሪያ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ቀጥል"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"መተግበሪያ ያጋሩ ወይም ይቅረጹ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ያቀናብሩ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ታሪክ"</string>
@@ -445,7 +433,7 @@
<string name="volume_odi_captions_content_description" msgid="4172765742046013630">"የሥዕል መግለጫ ጽሑፎች ንብርብር"</string>
<string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"አንቃ"</string>
<string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"አሰናክል"</string>
- <string name="sound_settings" msgid="8874581353127418308">"ድምጽ እና ንዝረት"</string>
+ <string name="sound_settings" msgid="8874581353127418308">"ድምፅ እና ንዝረት"</string>
<string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"ቅንብሮች"</string>
<string name="screen_pinning_title" msgid="9058007390337841305">"መተግበሪያ ተሰክቷል"</string>
<string name="screen_pinning_description" msgid="8699395373875667743">"ይሄ እስኪነቅሉት ድረስ በእይታ ውስጥ ያስቀምጠዋል። ለመንቀል ተመለስ እና አጠቃላይ ዕይታ የሚለውን ይጫኑ እና ይያዙ።"</string>
@@ -527,11 +515,11 @@
<string name="notification_silence_title" msgid="8608090968400832335">"ፀጥ ያለ"</string>
<string name="notification_alert_title" msgid="3656229781017543655">"ነባሪ"</string>
<string name="notification_automatic_title" msgid="3745465364578762652">"ራስ-ሰር"</string>
- <string name="notification_channel_summary_low" msgid="4860617986908931158">"ምንም ድምጽ ወይም ንዝረት የለም"</string>
- <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ምንም ድምጽ ወይም ንዝረት የለም እና በውይይት ክፍል ላይ አይታይም"</string>
+ <string name="notification_channel_summary_low" msgid="4860617986908931158">"ምንም ድምፅ ወይም ንዝረት የለም"</string>
+ <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ምንም ድምፅ ወይም ንዝረት የለም እና በውይይት ክፍል ላይ አይታይም"</string>
<string name="notification_channel_summary_default" msgid="3282930979307248890">"በእርስዎ የስልክ ቅንብሮች የሚወሰን ሆኖ ሊደውል ወይም ሊነዝር ይችላል"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"በእርስዎ የስልክ ቅንብሮች የሚወሰን ሆኖ ሊደውል ወይም ሊነዝር ይችላል። የ<xliff:g id="APP_NAME">%1$s</xliff:g> አረፋ ውይይቶች በነባሪነት።"</string>
- <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ይህ ማሳወቂያ ድምጽ ወይም ንዝረት መደረግ ካለበት ስርዓቱ እንዲወሰን ያድርጉት"</string>
+ <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ይህ ማሳወቂያ ድምፅ ወይም ንዝረት መደረግ ካለበት ስርዓቱ እንዲወሰን ያድርጉት"</string>
<string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;ሁኔታ:&lt;/b&gt; ለነባሪ ከፍ ተዋውቋል።"</string>
<string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;ሁኔታ:&lt;/b&gt; ወደ ዝምታ ዝቅ ተደርጓል"</string>
<string name="notification_channel_summary_automatic_promoted" msgid="1301710305149590426">"&lt;b&gt;ሁኔታ:&lt;/b&gt; ክፍተኛ ደረጃ ተሰጥቶታል"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 732c2e62c29f..8f996fb9794a 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"إشعار مستمر لجلسة تسجيل شاشة"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"هل تريد بدء التسجيل؟"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"‏أثناء التسجيل، يمكن أن يسجّل نظام Android أي معلومات حساسة مرئية على شاشتك أو يتم تشغيلها على جهازك. ويشمل ذلك كلمات المرور ومعلومات الدفع والصور والرسائل والمقاطع الصوتية."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"تسجيل الشاشة بالكامل"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"تسجيل محتوى تطبيق واحد"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"‏أثناء التسجيل، يمكن لنظام Android الوصول إلى كل العناصر المرئية على شاشتك أو التي يتم تشغيلها على جهازك، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"‏أثناء تسجيل محتوى أحد التطبيقات، يمكن لنظام Android الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"بدء التسجيل"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"تسجيل الصوت"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"صوت الجهاز"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"الصوت من جهازك، مثلاً الموسيقى والمكالمات ونغمات الرنين"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ستتمكن الخدمة التي تقدّم هذه الوظيفة من الوصول إلى كل المعلومات المرئية لك على الشاشة أو التي يتم تشغيلها على جهازك أثناء التسجيل أو الإرسال. ويشمل ذلك معلومات مثل كلمات المرور وتفاصيل الدفع والصور والرسائل والمقاطع الصوتية التي تشغِّلها."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"هل تريد بدء التسجيل أو الإرسال؟"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"هل تريد بدء التسجيل أو الإرسال باستخدام <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>؟"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"هل تريد السماح لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>بالمشاركة أو التسجيل؟"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"الشاشة بالكامل"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"تطبيق واحد"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"أثناء المشاركة أو التسجيل أو البث، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> الوصول إلى كل العناصر المرئية على شاشتك أو التي يتم تشغيلها على جهازك، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"أثناء مشاركة محتوى تطبيق أو تسجيله أو بثه، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"متابعة"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"مشاركة محتوى تطبيق أو تسجيله"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"إدارة"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"السجلّ"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index c2f8e4350df6..fe95fa024b93 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রীন ৰেকৰ্ডিং ছেশ্বন চলি থকা সময়ত পোৱা জাননী"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ৰেকৰ্ড কৰা আৰম্ভ কৰিবনে?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ৰেকৰ্ড কৰি থাকোঁতে, Android Systemএ আপোনাৰ স্ক্রীনত দৃশ্যমান হোৱা অথবা আপোনাৰ ডিভাইচত প্লে’ হৈ থকা যিকোনো সংবেনদশীল তথ্য কেপচাৰ কৰিব পাৰে। এইটোত পাছৱর্ড, পৰিশোধৰ তথ্য, ফট’, বার্তাসমূহ আৰু অডিঅ’ অন্তর্ভুক্ত হয়।"</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"গোটেই স্ক্ৰীনখন ৰেকৰ্ড কৰক"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"এটা এপ্ ৰেকৰ্ড কৰক"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"আপুনি ৰেকৰ্ড কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"আপুনি এপ এপ্ ৰেকৰ্ড কৰাৰ সময়ত সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ৰেকৰ্ডিং আৰম্ভ কৰক"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"অডিঅ’ ৰেকৰ্ড কৰক"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ডিভাইচৰ অডিঅ’"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"সংগীত, কল আৰু ৰিংট’নসমূহৰ দৰে আপোনাৰ ডিভাইচৰ পৰা কেপচাৰ কৰিব পৰা ধ্বনি"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"এই সুবিধাটো প্ৰদান কৰা সেৱাটোৱে আপোনাৰ স্ক্ৰীনত দৃশ্যমান হোৱা অথবা ৰেকর্ডিং অথবা কাষ্টিঙৰ সময়ত আপোনাৰ ডিভাইচত প্লে\' কৰা আটাইবোৰ তথ্যলৈ এক্সেছ পাব। এইটোত পাছৱর্ড, পৰিশোধৰ সবিশেষ, ফট\', বার্তাসমূহ আৰু আপুনি প্লে\' কৰা অডিঅ\'ৰ দৰে তথ্য অন্তর্ভুক্ত হয়।"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ জৰিয়তে ৰেকর্ডিং অথবা কাষ্টিং আৰম্ভ কৰিবনে ?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ক শ্বেয়াৰ অথবা ৰেকৰ্ড কৰিবলৈ অনুমতি দিবনে?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"সম্পূৰ্ণ স্ক্ৰীন"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"এটা একক এপ্"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"অব্যাহত ৰাখক"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"এটা এপ্ শ্বেয়াৰ অথবা ৰেকৰ্ড কৰক"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"পৰিচালনা"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
@@ -575,7 +563,7 @@
<string name="keyboard_key_dpad_down" msgid="2110172278574325796">"তললৈ"</string>
<string name="keyboard_key_dpad_left" msgid="8329738048908755640">"বাওঁফালে"</string>
<string name="keyboard_key_dpad_right" msgid="6282105433822321767">"সোঁফালে"</string>
- <string name="keyboard_key_dpad_center" msgid="4079412840715672825">"স্ক্ৰীণৰ মাজত"</string>
+ <string name="keyboard_key_dpad_center" msgid="4079412840715672825">"স্ক্ৰীনৰ মাজত"</string>
<string name="keyboard_key_tab" msgid="4592772350906496730">"Tab"</string>
<string name="keyboard_key_space" msgid="6980847564173394012">"স্পেচ"</string>
<string name="keyboard_key_enter" msgid="8633362970109751646">"এণ্টাৰ"</string>
@@ -596,7 +584,7 @@
<string name="keyboard_key_numpad_template" msgid="7316338238459991821">"নামপেড <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="notif_inline_reply_remove_attachment_description" msgid="7954075334095405429">"সংলগ্নক আঁতৰাওক"</string>
<string name="keyboard_shortcut_group_system" msgid="1583416273777875970">"ছিষ্টেম"</string>
- <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"গৃহ স্ক্ৰীণ"</string>
+ <string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"গৃহ স্ক্ৰীন"</string>
<string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"শেহতীয়াসমূহ"</string>
<string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"উভতি যাওক"</string>
<string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"জাননীসমূহ"</string>
@@ -693,7 +681,7 @@
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string>
<string name="high_temp_title" msgid="2218333576838496100">"ফ\'নটো গৰম হ\'বলৈ ধৰিছে"</string>
<string name="high_temp_notif_message" msgid="1277346543068257549">"ফ’নটো ঠাণ্ডা হৈ থকাৰ সময়ত কিছুমান সুবিধা উপলব্ধ নহয়।\nঅধিক তথ্যৰ বাবে টিপক"</string>
- <string name="high_temp_dialog_message" msgid="3793606072661253968">"আপোনাৰ ফ\'নটোৱে নিজে নিজে ঠাণ্ডা হ\'বলৈ স্বয়ংক্ৰিয়ভাৱে চেষ্টা কৰিব। আপুনি ফ\'নটো ব্যৱহাৰ কৰি থাকিব পাৰে কিন্তু ই লাহে লাহে চলিব পাৰে।\n\nফ\'নটো সম্পূৰ্ণভাৱে ঠাণ্ডা হোৱাৰ পিছত ই আগৰ নিচিনাকৈয়েই চলিব।"</string>
+ <string name="high_temp_dialog_message" msgid="3793606072661253968">"আপোনাৰ ফ\'নটোৱে নিজে নিজে ঠাণ্ডা হ\'বলৈ স্বয়ংক্ৰিয়ভাৱে চেষ্টা কৰিব। আপুনি ফ\'নটো ব্যৱহাৰ কৰি থাকিব পাৰে কিন্তু ই লাহে লাহে চলিব পাৰে।\n\nফ\'নটো সম্পূৰ্ণভাৱে ঠাণ্ডা হোৱাৰ পাছত ই আগৰ নিচিনাকৈয়েই চলিব।"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"আপোনাৰ ডিভাইচটো আনপ্লাগ কৰক"</string>
<string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"আপোনাৰ ডিভাইচটো চাৰ্জিং প’ৰ্টৰ ওচৰত গৰম হৈছে। যদি এইটো কোনো চার্জাৰ অথবা ইউএছবিৰ সহায়ক সামগ্ৰীৰ সৈতে সংযুক্ত হৈ আছে, ইয়াক আনপ্লাগ কৰক আৰু কে’বলডালো গৰম হ\'ব পাৰে, গতিকে যত্ন লওক।"</string>
@@ -722,7 +710,7 @@
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> চলি আছে"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"এপ্‌টো ইনষ্ট\'ল নকৰাকৈ খোলা হৈছে।"</string>
- <string name="instant_apps_message_with_help" msgid="1816952263531203932">"ইনষ্ট\'ল নকৰাকৈয়েই এপটো খোলা হৈছে। অধিক জানিবলৈ টিপক।"</string>
+ <string name="instant_apps_message_with_help" msgid="1816952263531203932">"ইনষ্ট\'ল নকৰাকৈয়েই এপ্‌টো খোলা হৈছে। অধিক জানিবলৈ টিপক।"</string>
<string name="app_info" msgid="5153758994129963243">"এপৰ তথ্য"</string>
<string name="go_to_web" msgid="636673528981366511">"ব্ৰাউজাৰলৈ যাওক"</string>
<string name="mobile_data" msgid="4564407557775397216">"ম’বাইল ডেটা"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index fa6da9413356..cbc7c8aea155 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущо известие за сесия за записване на екрана"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Да се стартира ли записът?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"По време на записване системата Android може да запише и поверителна информация, която е показана на екрана или възпроизвеждана на устройството ви. Това включва пароли, данни за плащане, снимки, съобщения и аудио."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Записване на целия екран"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Записване на едно приложение"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Когато записвате, Android има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Когато записвате приложение, Android има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Стартиране на записа"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Записване на звук"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Аудио от устройството"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук от устройството ви, като например музика, обаждания и мелодии"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Услугата, предоставяща тази функция, ще има достъп до цялата информация, която е видима на екрана или възпроизвеждана от устройството ви по време на записване или предаване. Това включва различна информация, като например пароли, данни за плащане, снимки, съобщения и възпроизвеждано аудио."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Да се стартира ли записване или предаване?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Да се стартира ли записване или предаване чрез <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Разрешавате ли на <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> да споделя и записва?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Цял екран"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Едно приложение"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Напред"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Споделяне или записване на приложение"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Управление"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 8dc5f33b8358..44fb0e610ef7 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রিন রেকর্ডিং সেশন চলার বিজ্ঞপ্তি"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"রেকর্ডিং শুরু করবেন?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"রেকর্ড করার সময়, আপনার স্ক্রিনে দেখানো বা ডিভাইসে চালানো যেকোনও ধরনের সংবেদনশীল তথ্য Android সিস্টেম ক্যাপচার করতে পারে। এর মধ্যে পাসওয়ার্ড, পেমেন্টের তথ্য, ফটো, মেসেজ এবং অডিও সম্পর্কিত তথ্য থাকে।"</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"সম্পূর্ণ স্ক্রিন রেকর্ড করুন"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"একটিমাত্র অ্যাপ রেকর্ড করুন"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"আপনার রেকর্ড করার সময়, স্ক্রিনে দেখা যায় বা ডিভাইসে খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি Android-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"কোনও অ্যাপ আপনার রেকর্ড করার সময়, সেই অ্যাপে দেখা যায় বা খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি Android-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"রেকর্ড করা শুরু করুন"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"অডিও রেকর্ড করুন"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ডিভাইস অডিও"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"মিউজিক, কল এবং রিংটোনগুলির মতো আপনার ডিভাইস থেকে সাউন্ড"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"রেকর্ড করা বা কাস্টিং করার সময় আপনার স্ক্রিনে দেখানো বা ডিভাইসে চালানো হয়েছে এমন সমস্ত তথ্যের অ্যাক্সেস এই ফাংশন প্রদানকারী পরিষেবার কাছে থাকবে। এর মধ্যে আপনার পাসওয়ার্ড, পেমেন্টের বিবরণ, ফটো, মেসেজ এবং যে অডিও আপনি চালান সেগুলি সম্পর্কিত তথ্য রয়েছে।"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"রেকর্ড অথবা কাস্টিং শুরু করতে চান?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> দিয়ে রেকর্ড করা বা কাস্টিং শুরু করবেন?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-কে শেয়ার বা রেকর্ড করার অনুমতি দেবেন?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"সম্পূর্ণ স্ক্রিন"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"একটি মাত্র অ্যাপ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"আপনার শেয়ার করা, রেকর্ড করা বা কাস্ট করার সময়, স্ক্রিনে দেখা যায় বা ডিভাইসে খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"কোনও অ্যাপ আপনার শেয়ার করা, রেকর্ড করা বা কাস্ট করার সময়, সেই অ্যাপে দেখা যায় বা খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"চালিয়ে যান"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"অ্যাপ শেয়ার বা রেকর্ড করা"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"সবকিছু সাফ করুন"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"পরিচালনা করুন"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 5e3fb3362719..172b14d8f6d0 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificació en curs d\'una sessió de gravació de la pantalla"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Vols iniciar la gravació?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Durant la gravació, el sistema Android pot capturar qualsevol informació sensible que es mostri a la pantalla o que es reprodueixi al dispositiu. Això inclou contrasenyes, informació de pagament, fotos, missatges i àudio."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grava la pantalla completa"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grava una sola aplicació"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mentre graves, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mentre graves una aplicació, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Inicia la gravació"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Grava l\'àudio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Àudio del dispositiu"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"So del dispositiu, com ara música, trucades i sons de trucada"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"El servei que ofereix aquesta funció tindrà accés a tota la informació visible a la teva pantalla o que es reprodueix al dispositiu mentre graves o emets contingut, com ara contrasenyes, detalls dels pagaments, fotos, missatges i àudio que reprodueixis."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vols començar a gravar o emetre contingut?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Vols començar a gravar o emetre contingut amb <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vols permetre que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparteixi o gravi?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tota la pantalla"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola aplicació"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continua"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Comparteix o grava una aplicació"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestiona"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index b22656c8787a..034fe9a33eb7 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Trvalé oznámení o relaci nahrávání"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Spustit nahrávání?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Při nahrávání může systém Android zaznamenávat citlivé údaje, které jsou viditelné na obrazovce nebo které jsou přehrávány na zařízení. Týká se to hesel, údajů o platbě, fotek, zpráv a zvuků."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Nahrát celou obrazovku"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Nahrát samostatnou aplikaci"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Během nahrávání má Android přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Během nahrávání aplikace má Android přístup k veškerému obsahu, který je v této aplikaci zobrazen nebo přehráván. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Spustit nahrávání"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Nahrávat zvuk"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvuk zařízení"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Zvuk ze zařízení, například hudba, hovory a vyzvánění"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Služba, která tuto funkci poskytuje, bude mít při nahrávání nebo odesílání přístup ke všem informacím, které jsou viditelné na obrazovce nebo které jsou přehrávány ze zařízení. Týká se to i hesel, údajů o platbě, fotek, zpráv a přehrávaných zvuků."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Začít nahrávat nebo odesílat?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Začít nahrávat nebo odesílat s aplikací <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Povolit aplikaci <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sdílení nebo nahrávání?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Celá obrazovka"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Samostatná aplikace"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Když sdílíte, nahráváte nebo odesíláte obsah, aplikace <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> má přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Když sdílíte, nahráváte nebo odesíláte aplikaci, aplikace <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> má přístup k veškerému obsahu, který je v této aplikaci zobrazen nebo přehráván. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Pokračovat"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Sdílení nebo nahrání aplikace"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Spravovat"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historie"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 9edb337763b8..03d12aff4ff1 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Konstant notifikation om skærmoptagelse"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Vil du starte optagelse?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Når du optager, kan Android-systemet registrere følsomme oplysninger, der er synlige på din skærm, eller som afspilles på din enhed. Dette inkluderer adgangskoder, betalingsoplysninger, fotos, meddelelser og lyd."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Optag hele skærmen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Optag én app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mens du optager, har Android adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mens du optager en app, har Android adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Start optagelse"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Optag lyd"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Enhedslyd"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Lyd fra din enhed såsom musik, opkald og ringetoner"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Tjenesten, der tilbyder denne funktion, får adgang til alle de oplysninger, der er synlige på din skærm, eller som afspilles på din enhed, når du optager eller caster. Dette omfatter oplysninger som f.eks. adgangskoder, betalingsoplysninger, billeder, beskeder og afspillet lyd."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vil du begynde at optage eller caste?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Vil du begynde at optage eller caste via <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vil du tillade, at <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> deler eller optager?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hele skærmen"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Én app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Når du deler, optager eller caster, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, optager eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsæt"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller optag en app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 76980658f41f..1d13c87fc548 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Fortlaufende Benachrichtigung für eine Bildschirmaufzeichnung"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Aufzeichnung starten?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Beim Aufnehmen kann das Android-System vertrauliche Informationen erfassen, die auf deinem Bildschirm angezeigt oder von deinem Gerät wiedergegeben werden. Das können Passwörter, Zahlungsinformationen, Fotos, Nachrichten und Audioinhalte sein."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gesamten Bildschirm aufnehmen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Eine einzelne App aufnehmen"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Während der Aufnahme hat Android Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Während der Aufnahme einer App hat Android Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Aufnahme starten"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Audio aufnehmen"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio des Geräts"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Audioinhalte auf deinem Gerät, wie Musik, Anrufe und Klingeltöne"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Der Anbieter dieser App erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise angezeigte Passwörter, Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Aufnahme oder Stream starten?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Aufnehmen oder Streamen mit der App \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" starten?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Zulassen, dass <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Inhalte teilt oder aufnimmt?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Gesamter Bildschirm"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Eine einzelne App"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Beim Teilen, Aufnehmen oder Übertragen hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Beim Teilen, Aufnehmen oder Übertragen einer App hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Weiter"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"App teilen oder aufnehmen"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Verwalten"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Verlauf"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index e9d1534442dc..0df5f16c0409 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación continua de una sesión de grabación de la pantalla"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"¿Empezar a grabar?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Mientras grabas, el sistema Android puede capturar información sensible que se muestre o se reproduzca en tu dispositivo, como contraseñas, datos de pago, fotos, mensajes y audio."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grabar toda la pantalla"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grabar una sola aplicación"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mientras grabes contenido, Android podrá acceder a todo lo que sea visible en tu pantalla o que reproduzcas en tu dispositivo. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mientras grabes una aplicación, Android podrá acceder a todo lo que muestre o reproduzca la aplicación. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Iniciar grabación"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Grabar audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio del dispositivo"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sonido de tu dispositivo, como música, llamadas y tonos de llamada"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"El servicio que ofrece esta función tendrá acceso a toda la información que se muestre en la pantalla o se reproduzca en el dispositivo mientras grabas o envías contenido, incluyendo contraseñas, detalles de pagos, fotos, mensajes y audios que reproduzcas."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"¿Empezar a grabar o enviar contenido?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"¿Iniciar grabación o el envío de contenido en <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"¿Permitir que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparta o grabe contenido?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Toda la pantalla"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola aplicación"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Cuando compartas, grabes o envíes contenido, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo lo que sea visible en tu pantalla o que reproduzcas en tu dispositivo. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o envíes una aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo lo que muestre o reproduzca la aplicación. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir o grabar una aplicación"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestionar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index fae8364fa55b..d42b39704594 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Pooleli märguanne ekraanikuva salvestamise seansi puhul"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Kas alustada salvestamist?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Salvestamise ajal võib Androidi süsteem jäädvustada tundlikku teavet, mis on ekraanikuval nähtav või mida seadmes esitatakse. See hõlmab paroole, makseteavet, fotosid, sõnumeid ja heli."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Kogu ekraanikuva salvestamine"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ühe rakenduse salvestamine"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Salvestamise ajal on Androidil juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Rakenduse salvestamise ajal on Androidil juurdepääs kõigele, mis on selles rakenduses nähtaval või mida selles esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Alusta salvestamist"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Heli salvestamine"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Seadme heli"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Seadmest pärinev heli, nt muusika, kõned ja helinad"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Seda funktsiooni pakkuv teenus saab juurdepääsu kogu teabele, mis on teie ekraanikuval nähtav või mida seadmes salvestamise või ülekande ajal esitatakse. See hõlmab teavet, nagu paroolid, maksete üksikasjad, fotod, sõnumid ja esitatav heli."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Kas alustada salvestamist või ülekannet?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Kas alustada rakendusega <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> salvestamist või ülekannet?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Kas lubada rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> jagada või salvestada?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Kogu ekraanikuva"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Üks rakendus"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kui jagate, salvestate või kannate üle, on rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kui jagate, salvestate või kannate rakendust üle, on rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jätka"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Rakenduse jagamine või salvestamine"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Haldamine"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Ajalugu"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index cf53e327b9a5..fc93a800e974 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Pantailaren grabaketa-saioaren jakinarazpen jarraitua"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Grabatzen hasi nahi duzu?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Pantaila grabatzen duzun bitartean, baliteke Android sistemak pantailan agertzen den edo gailuak erreproduzitzen duen kontuzko informazioa grabatzea; besteak beste, pasahitzak, ordainketa-informazioa, argazkiak, mezuak eta audioa."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grabatu pantaila osoko edukia"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grabatu aplikazio bakar bat"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Grabatzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztirako sarbidea du Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Aplikazio bat grabatzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Hasi grabatzen"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Grabatu audioa"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Gailuaren audioa"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Gailuko soinuak; adibidez, musika, deiak eta tonuak"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Zerbait grabatzen edo igortzen duzunean, pantailan ikus daitekeen edo gailuak erreproduzitzen duen informazio guztia atzitu ahalko du funtzio hori eskaintzen duen zerbitzuak; besteak beste, pasahitzak, ordainketen xehetasunak, argazkiak, mezuak eta erreproduzitzen dituzun audioak."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Grabatzen edo igortzen hasi nahi duzu?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioarekin grabatzen edo igortzen hasi nahi duzu?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Edukia partekatu edo grabatzeko baimena eman nahi diozu <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioari?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Pantaila osoa"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Aplikazio bakar bat"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Edukia partekatzen, grabatzen edo igortzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztirako sarbidea du <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Egin aurrera"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partekatu edo grabatu aplikazioak"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Kudeatu"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 14a65dc4bcd6..088234281cae 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Pysyvä ilmoitus näytön tallentamisesta"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Aloitetaanko tallennus?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Tallennuksen aikana Android-järjestelmä voi tallentaa mitä tahansa näytöllä näkyvää tai laitteen toistamaa arkaluontoista tietoa. Näitä tietoja ovat esimerkiksi salasanat, maksutiedot, kuvat, viestit ja audio."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Tallenna koko näyttö"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Tallenna yhtä sovellusta"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Kun tallennat, Android saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Kun tallennat sovellusta, Android saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Aloita tallennus"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Tallenna audiota"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Laitteen audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Musiikki, puhelut, soittoäänet ja muut äänet laitteesta"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Ominaisuuden tarjoavalla palvelulla on pääsy kaikkiin näytölläsi näkyviin tietoihin ja tietoihin laitteesi toistamasta sisällöstä tallennuksen tai striimauksen aikana. Näitä tietoja ovat esimerkiksi salasanat, maksutiedot, kuvat, viestit ja toistettava audiosisältö."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Aloitetaanko tallentaminen tai striimaus?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Haluatko, että <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aloittaa tallennuksen tai striimauksen?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Sallitaanko, että <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> jaetaan tai tallennetaan?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Koko näyttö"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Yksittäinen sovellus"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kun jaat, tallennat tai striimaat, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kun jaat, tallennat tai striimaat sovellusta, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jatka"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Jaa sovellus tai tallenna sen sisältöä"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tyhjennä kaikki"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Muuta asetuksia"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 9fdae3e3f0a8..a80b03791bd9 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notification en cours pour une session d\'enregistrement de l\'écran"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Démarrer l\'enregistrement ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Durant l\'enregistrement, le système Android peut capturer les infos sensibles affichées à l\'écran ou lues sur votre appareil. Cela inclut les mots de passe, les infos de paiement, les photos, les messages et l\'audio."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Enregistrer tout l\'écran"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Enregistrer une seule appli"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Lorsque vous enregistrez une appli, Android à accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Lorsque vous enregistrez une appli, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Lancer l\'enregistrement"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Enregistrer l\'audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio de l\'appareil"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Son provenant de l\'appareil (musique, appels et sonneries, etc.)"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Le service qui fournit cette fonction aura accès à toutes les infos visibles sur votre écran ou lues depuis votre appareil lors d\'un enregistrement ou de la diffusion d\'un contenu. Cela comprend, entre autres, vos mots de passe, les détails de vos paiements, vos photos, vos messages ou les contenus audio que vous écoutez."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Démarrer l\'enregistrement ou la diffusion ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Démarrer l\'enregistrement ou la diffusion avec <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Autoriser <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> à partager ou enregistrer ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tout l\'écran"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Une seule appli"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Lorsque vous partagez, enregistrez ou castez, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lorsque vous partagez, enregistrez ou castez une appli, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuer"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partager ou enregistrer une appli"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 36036aaf06b9..bb3894aa21e6 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación en curso sobre unha sesión de gravación de pantalla"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Queres iniciar a gravación?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Durante a gravación, o sistema Android pode captar información confidencial que apareza na pantalla ou se reproduza no dispositivo, como contrasinais, información de pago, fotos, mensaxes e audio."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gravar pantalla completa"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Gravar unha soa aplicación"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Durante a gravación, Android ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Cando gravas unha aplicación, Android ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Iniciar gravación"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Gravar audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio do dispositivo"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Son do dispositivo (por exemplo, música, chamadas e tons de chamada)"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"O servizo que proporciona esta función terá acceso a toda a información visible na pantalla ou reproducida desde o teu dispositivo mentres graves ou emitas contido. Isto inclúe información como contrasinais, detalles de pago, fotos, mensaxes e o audio que reproduzas."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Queres iniciar a gravación ou a emisión?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Queres comezar a gravar ou emitir contido con <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Queres permitir que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparta ou grave contido?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Pantalla completa"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Unha soa aplicación"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Cando compartes, gravas ou emites contido, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cando compartes, gravas ou emites unha aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir ou gravar unha aplicación"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todas"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Xestionar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 3d1b1ea56fb9..9dc6f67b1b93 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रिकॉर्ड सेशन के लिए जारी सूचना"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"क्या आपको रिकॉर्डिंग शुरू करनी है?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"रिकॉर्ड करते समय, Android सिस्टम आपकी स्क्रीन पर दिखने वाली या चलाई जाने वाली संवेदनशील जानकारी को कैप्चर कर सकता है. इसमें पासवर्ड, पैसे चुकाने से जुड़ी जानकारी, फ़ोटो, मैसेज, और ऑडियो शामिल हैं."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"पूरी स्क्रीन रिकॉर्ड करें"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"सिर्फ़ एक ऐप्लिकेशन रिकॉर्ड करें"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Android के पास, रिकॉर्ड करने के दौरान, स्क्रीन पर दिख रही हर चीज़ या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"कोई ऐप्लिकेशन रिकॉर्ड करने के दौरान, Android के पास उस पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"रिकॉर्ड करना शुरू करें"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ऑडियो रिकॉर्ड करें"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"डिवाइस ऑडियो"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"आपके डिवाइस से आने वाली आवाज़ जैसे कि संगीत, कॉल, और रिंगटोन"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"इस फ़ंक्शन को उपलब्ध कराने वाली सेवा, रिकॉर्ड या कास्ट करते समय, आपकी स्क्रीन पर दिखने वाली या चलाई जाने वाली जानकारी को ऐक्सेस कर सकती है. इसमें पासवर्ड, पैसे चुकाने से जुड़ी जानकारी, फ़ोटो, मैसेज, और चलाए जाने वाले ऑडियो शामिल हैं."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रिकॉर्डिंग या कास्ट करना शुरू करें?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> का इस्तेमाल करके रिकॉर्ड और कास्ट करना शुरू करें?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> को शेयर या रिकॉर्ड करने की अनुमति दें?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"पूरी स्क्रीन"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"सिर्फ़ एक ऐप्लिकेशन"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास स्क्रीन पर दिख रही हर चीज़ या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास उस ऐप्लिकेशन पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी रखें"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ऐप्लिकेशन शेयर करें या उसकी रिकॉर्डिंग करें"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"मैनेज करें"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index fed009f42025..5e5e249fc415 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Էկրանի տեսագրման աշխատաշրջանի ընթացիկ ծանուցում"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Սկսե՞լ տեսագրումը"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Տեսագրման ընթացքում Android համակարգը կարող է գրանցել անձնական տեղեկություններ, որոնք տեսանելի են էկրանին կամ նվագարկվում են ձեր սարքում։ Սա ներառում է այնպիսի տեղեկություններ, ինչպիսիք են, օրինակ, գաղտնաբառերը, վճարային տվյալները, լուսանկարները, հաղորդագրությունները և նվագարկվող աուդիո ֆայլերը։"</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Տեսագրել ամբողջ էկրանը"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Տեսագրել մեկ հավելված"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Երբ դուք տեսագրում եք էկրանը, Android-ին հասանելի է դառնում այն ամենը, ինչ տեսանելի է էկրանին և նվագարկվում է ձեր սարքում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Երբ դուք տեսագրում եք որևէ հավելվածի էկրանը, Android-ին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Սկսել տեսագրումը"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Ձայնագրել"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Սարքի ձայները"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Ձեր սարքի ձայները, օրինակ՝ երաժշտությունը, զանգերն ու զանգերանգները"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Ձայնագրման և հեռարձակման ընթացքում ծառայությունների մատակարարին հասանելի կլինեն ձեր սարքի էկրանին ցուցադրվող տեղեկությունները և ձեր սարքով նվագարկվող նյութերը։ Սա ներառում է այնպիսի տեղեկություններ, ինչպիսիք են, օրինակ, գաղտնաբառերը, վճարային տվյալները, լուսանկարները, հաղորդագրությունները և նվագարկվող աուդիո ֆայլերը։"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Սկսե՞լ ձայնագրումը կամ հեռարձակումը"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Սկսե՞լ ձայնագրումը կամ հեռարձակումը <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածով"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Թույլատրե՞լ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին ցուցադրել կամ տեսագրել էկրանը"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ամբողջ էկրանը"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Մեկ հավելված"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք էկրանը, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին հասանելի է դառնում այն ամենը, ինչ տեսանելի է էկրանին և նվագարկվում է ձեր սարքում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Շարունակել"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Հավելվածի էկրանի ցուցադրում կամ տեսագրում"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Կառավարել"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Պատմություն"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 01569000b915..e372c3735478 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notifikasi yang sedang berjalan untuk sesi rekaman layar"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Mulai merekam?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Saat merekam, Sistem Android dapat ikut merekam informasi sensitif yang terlihat di layar atau diputar di perangkat Anda. Informasi ini mencakup sandi, info pembayaran, foto, pesan, dan audio."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Rekam seluruh layar"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Rekam satu aplikasi"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Saat Anda merekam, Android akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Saat Anda merekam aplikasi, Android akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Mulai merekam"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Rekam audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio perangkat"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Suara dari perangkat Anda, seperti musik, panggilan, dan nada dering"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Layanan yang menyediakan fungsi ini akan memiliki akses ke semua informasi yang terlihat di layar atau diputar dari perangkat saat merekam atau melakukan transmisi. Ini mencakup informasi seperti sandi, detail pembayaran, foto, pesan, dan audio yang Anda putar."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Mulai merekam atau melakukan transmisi?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Mulai merekam atau melakukan transmisi dengan <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Izinkan <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> untuk membagikan atau merekam?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Seluruh layar"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Satu aplikasi"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Jika Anda membagikan, merekam, atau mentransmisikan, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Lanjutkan"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Bagikan atau rekam aplikasi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Kelola"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histori"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 7622ad4ed87b..136db9e92afc 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Áframhaldandi tilkynning fyrir skjáupptökulotu"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Hefja upptöku?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Á meðan tekið er upp getur Android kerfið fangað viðkvæmar upplýsingar sem sjást á skjánum eða spilast í tækinu. Þar á meðal eru upplýsingar á borð við aðgangsorð, greiðsluupplýsingar, myndir, skilaboð og hljóð."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Taka upp allan skjáinn"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Taka upp eitt forrit"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Þegar þú tekur upp hefur Android aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Þegar þú tekur upp forrit hefur Android aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Hefja upptöku"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Taka upp hljóð"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Hljóð tækis"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Hljóð úr tækinu á borð við tónlist, símtöl og hringitóna"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Þjónustan sem býður upp á þennan eiginleika fær aðgang að öllum upplýsingum sem sjást á skjánum eða eru spilaðar í tækinu á meðan upptaka eða útsending er í gangi, þar á meðal aðgangsorði, greiðsluupplýsingum, myndum, skilaboðum og hljóðefni sem þú spilar."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Viltu hefja upptöku eða útsendingu?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Viltu hefja upptöku eða útsendingu með <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Leyfa <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> að deila eða taka upp?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Allur skjárinn"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Eitt forrit"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Þegar þú deilir, tekur upp eða sendir út hefur<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Þegar þú deilir, tekur upp eða sendir út forrit hefur <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Áfram"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deila eða taka upp forrit"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Stjórna"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Ferill"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index d6631e7683b0..df3e6fd6007c 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Экранды бейнеге жазудың ағымдағы хабарландыруы"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Жазу басталсын ба?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Android жүйесі экранда көрсетілетін немесе құрылғыда ойнатылатын құпия ақпаратты жазып алуы мүмкін. Ондай ақпаратқа құпия сөздер, төлем ақпараты, фотосуреттер, хабарлар және аудио жатады."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Бүкіл экранды жазу"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Жалғыз қолданбаны жазу"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Жазу кезінде Android жүйесі экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Қолданба экранын жазу кезінде Android жүйесі қолданбада көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Жазуды бастау"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Аудио жазу"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Құрылғыдан шығатын дыбыс"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Музыка, қоңыраулар және рингтондар сияқты құрылғыдан шығатын дыбыс"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Осы функцияны ұсынатын қызмет жазу не трансляциялау кезінде экранда көрсетілетін немесе құрылғыда дыбысталатын ақпаратты пайдалана алады. Бұған құпия сөздер, төлем туралы мәліметтер, суреттер, хабарлар және аудиоматериалдар кіреді."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Жазу немесе трансляциялау басталсын ба?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> арқылы жазу немесе трансляциялау басталсын ба?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасына экранды бөлісуге не жазуға рұқсат берілсін бе?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Бүкіл экран"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Жалғыз қолданба"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Бөлісу, жазу не трансляциялау кезінде <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасы экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Қолданба экранын бөлісу, жазу не трансляциялау кезінде <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасы онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Жалғастыру"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Қолданба экранын бөлісу не жазу"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазалау"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Басқару"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Тарих"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 713d20d130fe..83a5e87260ca 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೆಶನ್‌ಗಾಗಿ ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಅಧಿಸೂಚನೆ"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ರೆಕಾರ್ಡಿಂಗ್ ಸಮಯದಲ್ಲಿ, ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ಗೋಚರಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲಾದ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು Android ಸಿಸ್ಟಂ ಕ್ಯಾಪ್ಚರ್ ಮಾಡಬಹುದು. ಇದು ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ಮಾಹಿತಿ, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೋವನ್ನು ಒಳಗೊಂಡಿದೆ."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ಸಂಪೂರ್ಣ ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ಒಂದೇ ಆ್ಯಪ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ನೀವು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್‌ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸಿ"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ಆಡಿಯೋ ರೆಕಾರ್ಡ್‌ ಮಾಡಿ"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ಸಾಧನದ ಆಡಿಯೋ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ನಿಮ್ಮ ಸಾಧನದ ಧ್ವನಿ ಉದಾ: ಸಂಗೀತ, ಕರೆಗಳು ಮತ್ತು ರಿಂಗ್‌ಟೋನ್‌ಗಳು"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ಈ ವೈಶಿಷ್ಟ್ಯವು ಒದಗಿಸುವ ಸೇವೆಗಳು, ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಗೋಚರಿಸುವ ಅಥವಾ ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಾಗ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಎಲ್ಲಾ ಮಾಹಿತಿಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುತ್ತವೆ. ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಫೋಟೋಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಆಡಿಯೋ ಪ್ಲೇಬ್ಯಾಕ್‌ನಂತಹ ಮಾಹಿತಿಯನ್ನು ಇದು ಒಳಗೊಂಡಿದೆ."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ರೆಕಾರ್ಡಿಂಗ್ ಅಥವಾ ಬಿತ್ತರಿಸುವಿಕೆಯನ್ನು ಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಮೂಲಕ ರೆಕಾರ್ಡಿಂಗ್, ಬಿತ್ತರಿಸುವುದನ್ನು ಪ್ರಾರಂಭಿಸುವುದೇ?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ಹಂಚಿಕೊಳ್ಳಲು ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಲು <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಅನ್ನು ಅನುಮತಿಸಬೇಕೆ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ಸಂಪೂರ್ಣ ಸ್ಕ್ರೀನ್"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ಒಂದೇ ಆ್ಯಪ್"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ನೀವು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್‌ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ಮುಂದುವರಿಸಿ"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಿ ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ನಿರ್ವಹಿಸಿ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ಇತಿಹಾಸ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 290c51724ec7..f3fb7a085549 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"화면 녹화 세션에 관한 지속적인 알림"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"녹화를 시작하시겠습니까?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Android 시스템이 녹화 중에 화면에 표시되거나 기기에서 재생되는 민감한 정보를 캡처할 수 있습니다. 여기에는 비밀번호, 결제 정보, 사진, 메시지 및 오디오가 포함됩니다."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"전체 화면 녹화"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"단일 앱 녹화"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"녹화할 때 Android에서 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"앱을 녹화할 때 Android에서 해당 앱에서 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"녹화 시작"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"오디오 녹음"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"기기 오디오"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"음악, 통화, 벨소리와 같이 기기에서 나는 소리"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"이 기능을 제공하는 서비스는 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"녹화 또는 전송을 시작하시겠습니까?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>으로 녹화 또는 전송을 시작하시겠습니까?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 공유 또는 녹화를 허용할까요?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"전체 화면"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"단일 앱"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"공유하거나 녹화하거나 전송할 때 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 앱에서 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"앱을 공유하거나 녹화하거나 전송할 때는 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"계속"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"앱 공유 또는 녹화"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"기록"</string>
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index 89191984b9e8..aefd9981d02e 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -18,4 +18,42 @@
<style name="BrightnessDialogContainer" parent="@style/BaseBrightnessDialogContainer">
<item name="android:layout_width">360dp</item>
</style>
+
+ <style name="AuthCredentialHeaderStyle">
+ <item name="android:paddingStart">48dp</item>
+ <item name="android:paddingEnd">24dp</item>
+ <item name="android:paddingTop">48dp</item>
+ <item name="android:paddingBottom">10dp</item>
+ <item name="android:gravity">top|left</item>
+ </style>
+
+ <style name="AuthCredentialPatternContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">320dp</item>
+ <item name="android:maxWidth">320dp</item>
+ <item name="android:minHeight">200dp</item>
+ <item name="android:minWidth">200dp</item>
+ <item name="android:paddingHorizontal">60dp</item>
+ <item name="android:paddingVertical">20dp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">6dp</item>
+ <item name="android:textSize">36dp</item>
+ <item name="android:focusable">true</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Subtitle">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">6dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Description">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">6dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+
</resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 9cb8bfdff771..5a63b626c81c 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Aktīvs paziņojums par ekrāna ierakstīšanas sesiju"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Vai sākt ierakstīšanu?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Ierakstīšanas laikā Android sistēmā var tikt tverta jebkura sensitīvā informācija, kas ir redzama jūsu ekrānā vai tiek atskaņota jūsu ierīcē. Šī informācija ir paroles, maksājumu informācija, fotoattēli, ziņojumi un audio."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Ierakstīt visu ekrānu"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ierakstīt vienu lietotni"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Ierakstīšanas laikā Android var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Lietotnes ierakstīšanas laikā Android var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Sākt ierakstīšanu"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Ierakstīt audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Ierīces audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Skaņa no jūsu ierīces, piemēram, mūzika, sarunas un zvana signāli"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Pakalpojums, kas nodrošina šo funkciju, iegūs piekļuvi visai informācijai, kas ierakstīšanas vai apraides laikā tiks rādīta jūsu ekrānā vai atskaņota jūsu ierīcē. Atļauja attiecas uz tādu informāciju kā paroles, maksājumu informācija, fotoattēli, ziņojumi un jūsu atskaņotais audio saturs."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vai vēlaties sākt ierakstīšanu/apraidi?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Vai vēlaties sākt ierakstīšanu vai apraidi, izmantojot lietotni <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vai atļaujat lietotnei <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> veikt kopīgošanu vai ierakstīšanu?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Viss ekrāns"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Viena lietotne"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kopīgošanas, ierakstīšanas vai apraides laikā <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lietotnes kopīgošanas, ierakstīšanas vai apraides laikā <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Turpināt"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Lietotnes kopīgošana vai ierakstīšana"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Pārvaldīt"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Vēsture"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index bf1c262008fc..7611822c689c 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ഒരു സ്ക്രീൻ റെക്കോർഡിംഗ് സെഷനായി നിലവിലുള്ള അറിയിപ്പ്"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"റെക്കോർഡിംഗ് ആരംഭിക്കണോ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"റെക്കോർഡ് ചെയ്യുമ്പോൾ, നിങ്ങളുടെ സ്‌ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് തന്ത്രപ്രധാന വിവരങ്ങളും Android സിസ്റ്റത്തിന് പകർത്താനാവും. പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിവരം, ഫോട്ടോകൾ, സന്ദേശങ്ങൾ, ഓഡിയോ എന്നിവ ഇതിൽ ഉൾപ്പെടുന്നു."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"പൂർണ സ്ക്രീൻ റെക്കോർഡ് ചെയ്യൂ"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ഒറ്റ ആപ്പ് റെക്കോർഡ് ചെയ്യുക"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"റെക്കോർഡ് ചെയ്യുമ്പോൾ, Android-ന് സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ഒരു ആപ്പ് റെക്കോർഡ് ചെയ്യുമ്പോൾ, Android-ന് ആ ആപ്പിൽ കാണിക്കുന്നതോ പ്ലേ ചെയ്യുന്നതോ ആയ എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"റെക്കോർഡിംഗ് ആരംഭിക്കുക"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ഓഡിയോ റെക്കോർഡ് ചെയ്യുക"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ഉപകരണത്തിന്റെ ഓഡിയോ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"സംഗീതം, കോളുകൾ, റിംഗ്‌ടോണുകൾ എന്നിവപോലെ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്നുള്ള ശബ്ദം"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"റെക്കോർഡ് ചെയ്യുമ്പോഴോ കാസ്‌റ്റ് ചെയ്യുമ്പോഴോ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് പ്ലേ ചെയ്യുന്നതോ നിങ്ങളുടെ സ്‌ക്രീനിൽ ദൃശ്യമാകുന്നതോ ആയ എല്ലാ വിവരങ്ങളിലേക്കും ഈ ഫംഗ്‌ഷൻ ലഭ്യമാക്കുന്ന സേവനത്തിന് ആക്‌സസ് ഉണ്ടായിരിക്കും. നിങ്ങൾ പ്ലേ ചെയ്യുന്ന ഓഡിയോ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, പാസ്‌വേഡുകൾ എന്നിവ പോലുള്ള വിവരങ്ങൾ ഇതിൽ ഉൾപ്പെടുന്നു."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"റെക്കോർഡ് ചെയ്യൽ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യൽ ആരംഭിക്കണോ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ഉപയോഗിച്ച് റെക്കോർഡ് ചെയ്യൽ അല്ലെങ്കിൽ കാസ്‌റ്റ് ചെയ്യൽ ആരംഭിക്കണോ?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"പങ്കിടാനോ റെക്കോർഡ് ചെയ്യാനോ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിനെ അനുവദിക്കണോ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"മുഴുവൻ സ്‌ക്രീൻ"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ഒറ്റ ആപ്പ്"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്‌മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"തുടരുക"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ഒരു ആപ്പ് പങ്കിടുക അല്ലെങ്കിൽ റെക്കോർഡ് ചെയ്യുക"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്‌ക്കുക"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"മാനേജ് ചെയ്യുക"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ചരിത്രം"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index f7b8ef0446d4..2f96f3397306 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रेकॉर्ड सत्रासाठी सुरू असलेली सूचना"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"रेकॉर्डिंग सुरू करायचे आहे का?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"रेकॉर्डिंग करताना, Android सिस्टीम तुमच्या स्क्रीनवर दिसणारी किंवा तुमच्या डिव्हाइसवर प्ले केलेली कोणतीही संवेदनशील माहिती कॅप्चर करू शकते. यात पासवर्ड, पेमेंट माहिती, फोटो, मेसेज आणि ऑडिओचा समावेश आहे."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"संपूर्ण स्क्रीन रेकॉर्ड करा"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"एकच अ‍ॅप रेकॉर्ड करा"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"तुम्ही रेकॉर्ड करत असताना, Android ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"तुम्ही अ‍ॅप रेकॉर्ड करत असताना, Android ला त्या अ‍ॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"रेकॉर्डिंग सुरू करा"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ऑडिओ रेकॉर्ड करा"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"डिव्हाइस ऑडिओ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"तुमच्या डिव्हाइसवरील आवाज, जसे की संगीत, कॉल आणि रिंगटोन"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"हे कार्य पुरवणाऱ्या सेवेस तुमच्या स्क्रीनवर दृश्यमान असलेल्या किंवा रेकॉर्ड किंवा कास्ट करताना तुमच्या डिव्हाइसमधून प्ले केलेल्या सर्व माहितीचा अ‍ॅक्सेस असेल. यामध्ये पासवर्ड, पेमेंट तपशील, फोटो, मेसेज आणि तुम्ही प्ले केलेला ऑडिओ यासारख्या माहितीचा समावेश असतो."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रेकॉर्ड करणे किंवा कास्ट करणे सुरू करायचे का ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ने रेकॉर्ड करणे किंवा कास्ट करणे सुरू करायचे का?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला शेअर किंवा रेकॉर्ड करण्याची अनुमती द्यायची आहे का?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"संपूर्ण स्क्रीन"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"एक अ‍ॅप"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"तुम्ही शेअर, रेकॉर्ड किंवा कास्ट करत असताना, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तुम्ही अ‍ॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला त्या अ‍ॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"पुढे सुरू ठेवा"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"अ‍ॅप शेअर किंवा रेकॉर्ड करा"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थापित करा"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index a874e4588e25..fe1b908e098b 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ဖန်သားပြင် ရိုက်ကူးသည့် စက်ရှင်အတွက် ဆက်တိုက်လာနေသော အကြောင်းကြားချက်"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"စတင် ရိုက်ကူးမလား။"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ရိုက်ကူးနေစဉ်အတွင်း Android စနစ်သည် သင့်ဖန်သားပြင်ပေါ်တွင် မြင်နိုင်သော (သို့) သင့်စက်ပစ္စည်းတွင် ဖွင့်ထားသော အရေးကြီးသည့် အချက်အလက်များကို ရိုက်ယူနိုင်သည်။ ၎င်းတွင် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ ဓာတ်ပုံ၊ မက်ဆေ့ဂျ်နှင့် အသံများ ပါဝင်သည်။"</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ဖန်သားပြင်တစ်ခုလုံးရိုက်ကူးရန်"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"အက်ပ်တစ်ခုတွင် ရိုက်ကူးရန်"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ရိုက်ကူးနေစဉ် Android သည် သင့်ဖန်သားပြင်ရှိ မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"အက်ပ်တစ်ခုကို ရိုက်ကူးနေစဉ် Android သည် ၎င်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"စတင်ရိုက်ကူးရန်"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"အသံဖမ်းရန်"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"စက်ပစ္စည်းအသံ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"သီချင်း၊ ဖုန်းခေါ်ဆိုမှုနှင့် ဖုန်းမြည်သံကဲ့သို့ သင့်စက်ပစ္စည်းမှ အသံ"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ဤဝန်ဆောင်မှုသည် ရိုက်ကူးဖမ်းယူနေစဉ် (သို့) ကာစ်လုပ်နေစဉ်အတွင်း သင့်ဖန်သားပြင်တွင် မြင်ရသော (သို့) သင့်စက်တွင် ဖွင့်ထားသော အချက်အလက်အားလုံးကို ကြည့်နိုင်ပါမည်။ ၎င်းတွင် စကားဝှက်များ၊ ငွေပေးချေမှုအသေးစိတ်များ၊ ဓာတ်ပုံများ၊ မက်ဆေ့ဂျ်များနှင့် သင်ဖွင့်သည့်အသံကဲ့သို့သော အချက်အလက်များ ပါဝင်သည်။"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ရိုက်ကူးဖမ်းယူခြင်း (သို့) ကာစ်လုပ်ခြင်း စတင်မလား။"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> နှင့် ဖမ်းယူခြင်း သို့မဟုတ် ကာစ်လုပ်ခြင်း စတင်မလား။"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"မျှဝေရန် (သို့) ရိုက်ကူးရန် <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ကို ခွင့်ပြုမလား။"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ဖန်သားပြင်တစ်ခုလုံး"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"အက်ပ်တစ်ခုတွင်"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် သင့်ဖန်သားပြင်ရှိ မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"အက်ပ်ဖြင့် မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် ၎င်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ရှေ့ဆက်ရန်"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"အက်ပ် မျှဝေခြင်း (သို့) ရိုက်ကူးခြင်း"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"စီမံရန်"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"မှတ်တမ်း"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 6dd9d8f4b7f1..951eb8c46436 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Vedvarende varsel for et skjermopptak"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Vil du starte et opptak?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Under opptak kan Android-systemet registrere all sensitiv informasjon som er synlig på skjermen eller spilles av på enheten. Dette inkluderer passord, betalingsinformasjon, bilder, meldinger og lyd."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Ta opp hele skjermen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Ta opp én app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Når du tar opp noe, har Android tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Når du tar opp en app, har Android tilgang til alt som vises eller spilles av i appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Start opptaket"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Spill inn lyd"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Enhetslyd"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Lyd fra enheten, f.eks. musikk, samtaler og ringelyder"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Tjenesten som leverer denne funksjonen, får tilgang til all informasjon som er synlig på skjermen din, eller som spilles av fra enheten når du tar opp eller caster. Dette inkluderer informasjon som passord, betalingsopplysninger, bilder, meldinger og lyd du spiller av."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vil du starte opptak eller casting?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Vil du starte opptak eller casting med <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vil du gi <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tillatelse til å dele eller ta opp?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hele skjermen"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Én app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Når du deler, tar opp eller caster noe, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, tar opp eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsett"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller ta opp en app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Logg"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 2362b401df7d..35112a7b1a03 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"कुनै स्क्रिन रेकर्ड गर्ने सत्रका लागि चलिरहेको सूचना"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"रेकर्ड गर्न थाल्ने हो?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"रेकर्ड गर्दा, Android सिस्टमले तपाईंको स्क्रिनमा देखिने वा तपाईंको डिभाइसमा प्ले गरिने सबै संवेदनशील जानकारी रेकर्ड गर्न सक्छ। यो जानकारीमा पासवर्ड, भुक्तानीसम्बन्धी जानकारी, फोटो, सन्देश र अडियो समावेश हुन्छ।"</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"पूरै स्क्रिन रेकर्ड गर्नुहोस्"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"एउटा एप मात्र रेकर्ड गर्नुहोस्"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"तपाईंले रेकर्ड गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले रेकर्ड गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"तपाईंले रेकर्ड गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले रेकर्ड गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"रेकर्ड गर्न थाल्नुहोस्"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"अडियो रेकर्ड गरियोस्"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"डिभाइसको अडियो"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"तपाईंको डिभाइसका सङ्गीत, कल र रिङटोन जस्ता साउन्ड"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"यो कार्य गर्ने सेवाले तपाईंको स्क्रिनमा देख्न सकिने सबै जानकारी अथवा रेकर्ड वा कास्ट गर्दा तपाईंको डिभाइसबाट प्ले गरिएका कुरा हेर्न तथा प्रयोग गर्न सक्छ। यसले हेर्न तथा प्रयोग गर्न सक्ने कुरामा पासवर्ड, भुक्तानीका विवरण, फोटो, सन्देश र तपाईंले प्ले गर्ने अडियो कुराहरू समावेश हुन सक्छन्।"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रेकर्ड गर्न वा cast गर्न थाल्ने हो?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> मार्फत रेकर्ड गर्न वा cast गर्न थाल्ने हो?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> लाई सेयर गर्न वा रेकर्ड गर्न दिने हो?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"पूर्ण स्क्रिन"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"एकल एप"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले सेयर, रेकर्ड वा कास्ट गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी राख्नुहोस्"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"सेयर वा रेकर्ड गर्नका लागि एप चयन गर्नुहोस्"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थित गर्नुहोस्"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 01d0d9fc2216..04c3774be53a 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ଏକ ସ୍କ୍ରି‍ନ୍‍ ରେକର୍ଡ୍‍ ସେସନ୍‍ ପାଇଁ ଚାଲୁଥିବା ବିଜ୍ଞପ୍ତି"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ରେକର୍ଡିଂ ଆରମ୍ଭ କରିବେ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ରେକର୍ଡିଂ ସମୟରେ, Android ସିଷ୍ଟମ୍ ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ଚାଲୁଥିବା ଯେ କୌଣସି ସମ୍ବେଦନଶୀଳ ସୂଚନାକୁ କ୍ୟାପଚର୍ କରିପାରିବ। ଏଥିରେ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ସୂଚନା, ଫଟୋ, ମେସେଜ ଏବଂ ଅଡିଓ ଅନ୍ତର୍ଭୁକ୍ତ।"</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ ରେକର୍ଡ କର"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ଏକ ସିଙ୍ଗଲ ଆପ ରେକର୍ଡ କରନ୍ତୁ"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ଆପଣ ରେକର୍ଡିଂ କରିବା ବେଳେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ଆପଣ ଏକ ଆପ ରେକର୍ଡିଂ କରିବା ବେଳେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ରେକର୍ଡିଂ ଆରମ୍ଭ କରନ୍ତୁ"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ଅଡିଓ ରେକର୍ଡ କରନ୍ତୁ"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ଡିଭାଇସ୍ ଅଡିଓ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ଆପଣଙ୍କ ଡିଭାଇସରୁ ସାଉଣ୍ଡ, ଯେପରିକି ସଙ୍ଗୀତ, କଲ୍ ଏବଂ ରିଂଟୋନଗୁଡ଼ିକ"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ବେଳେ ଆପଣଙ୍କର ଡିଭାଇସରେ ଦେଖାଯାଉଥିବା ବା ଆପଣଙ୍କ ଡିଭାଇସରୁ ପ୍ଲେ କରାଯାଉଥିବା ସବୁ ସୂଚନାକୁ ଏହି ଫଙ୍କସନ୍ ପ୍ରଦାନ କରୁଥିବା ସେବାର ଆକ୍ସେସ୍ ରହିବ। ପାସ୍‌ୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ଫଟୋ, ମେସେଜ୍ ଏବଂ ଆପଣ ଚଲାଉଥିବା ଅଡିଓ ପରି ସୂଚନା ଏଥିରେ ଅନ୍ତର୍ଭୁକ୍ତ ଅଛି।"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ଆରମ୍ଭ କରିବେ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ସହ ରେକର୍ଡିଂ ବା କାଷ୍ଟିଂ ଆରମ୍ଭ କରିବେ?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ସେୟାର କିମ୍ବା ରେକର୍ଡ କରିବା ପାଇଁ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ଏକ ସିଙ୍ଗଲ ଆପ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ ସେହି ଆପର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ଜାରି ରଖନ୍ତୁ"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ଏକ ଆପକୁ ସେୟାର କିମ୍ବା ରେକର୍ଡ କରନ୍ତୁ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ସମସ୍ତ ଖାଲି କରନ୍ତୁ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ପରିଚାଳନା କରନ୍ତୁ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ଇତିହାସ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index e9f4cf9862bb..5f9709a9f46a 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ਕਿਸੇ ਸਕ੍ਰੀਨ ਰਿਕਾਰਡ ਸੈਸ਼ਨ ਲਈ ਚੱਲ ਰਹੀ ਸੂਚਨਾ"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ਕੀ ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਕਰਨੀ ਹੈ?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ਰਿਕਾਰਡਿੰਗ ਕਰਨ ਵੇਲੇ, Android ਸਿਸਟਮ ਕੋਈ ਵੀ ਅਜਿਹੀ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਕੈਪਚਰ ਕਰ ਸਕਦਾ ਹੈ ਜੋ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਣਯੋਗ ਹੈ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਜਾਂਦੀ ਹੈ। ਇਸ ਵਿੱਚ ਪਾਸਵਰਡ, ਭੁਗਤਾਨ ਵੇਰਵੇ, ਫ਼ੋਟੋਆਂ, ਸੁਨੇਹੇ ਅਤੇ ਆਡੀਓ ਸ਼ਾਮਲ ਹਨ।"</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ਇਕਹਿਰੀ ਐਪ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਰਿਕਾਰਡਿੰਗ ਕਰਨ ਵੇਲੇ, Android ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਰਿਕਾਰਡਿੰਗ ਕਰਨ ਵੇਲੇ, Android ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਕਰੋ"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ਆਡੀਓ ਰਿਕਾਰਡ ਕਰੋ"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"ਡੀਵਾਈਸ ਆਡੀਓ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਧੁਨੀ, ਜਿਵੇਂ ਕਿ ਸੰਗੀਤ, ਕਾਲਾਂ ਅਤੇ ਰਿੰਗਟੋਨਾਂ"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"ਇਹ ਫੰਕਸ਼ਨ ਪ੍ਰਦਾਨ ਕਰਨ ਵਾਲੀ ਸੇਵਾ ਕੋਲ ਸਾਰੀ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਹੋਵੇਗੀ ਜੋ ਕਿ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਣਯੋਗ ਹੁੰਦੀ ਹੈ ਜਾਂ ਰਿਕਾਰਡ ਜਾਂ ਕਾਸਟ ਕਰਨ ਵੇਲੇ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਜਾਂਦੀ ਹੈ। ਇਸ ਵਿੱਚ ਪਾਸਵਰਡ, ਭੁਗਤਾਨ ਵੇਰਵੇ, ਫ਼ੋਟੋਆਂ, ਸੁਨੇਹੇ ਅਤੇ ਤੁਹਾਡੇ ਵੱਲੋਂ ਚਲਾਏ ਆਡੀਓ ਦੀ ਜਾਣਕਾਰੀ ਸ਼ਾਮਲ ਹੁੰਦੀ ਹੈ।"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ਕੀ ਰਿਕਾਰਡ ਜਾਂ ਕਾਸਟ ਕਰਨਾ ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਨਾਲ ਰਿਕਾਰਡਿੰਗ ਜਾਂ ਕਾਸਟ ਕਰਨਾ ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"ਕੀ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਨੂੰ ਸਾਂਝਾ ਕਰਨ ਜਾਂ ਰਿਕਾਰਡ ਕਰਨ ਲਈ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ਇਕਹਿਰੀ ਐਪ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ਜਾਰੀ ਰੱਖੋ"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ਐਪ ਨੂੰ ਸਾਂਝਾ ਕਰੋ ਜਾਂ ਰਿਕਾਰਡ ਕਰੋ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ਇਤਿਹਾਸ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index dad40e10db75..563f8dc89638 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Stałe powiadomienie o sesji rejestrowania zawartości ekranu"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Rozpocząć nagrywanie?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Podczas nagrywania system Android może rejestrować wszelkie informacje poufne wyświetlane na ekranie lub odtwarzane na urządzeniu. Dotyczy to m.in. haseł, szczegółów płatności, zdjęć, wiadomości i odtwarzanych dźwięków."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Nagrywaj cały ekran"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Nagrywaj pojedynczą aplikację"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Podczas nagrywania Android ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Podczas nagrywania treści z aplikacji Android ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Zacznij nagrywać"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Nagraj dźwięk"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Dźwięki z urządzenia"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Dźwięki odtwarzane na urządzeniu, na przykład muzyka, połączenia i dzwonki"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Podczas nagrywania i przesyłania usługa udostępniająca tę funkcję będzie miała dostęp do wszystkich informacji widocznych na ekranie lub odtwarzanych na urządzeniu. Dotyczy to m.in. haseł, szczegółów płatności, zdjęć, wiadomości i odtwarzanych dźwięków."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Rozpocząć nagrywanie lub przesyłanie?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Rozpocząć nagrywanie lub przesyłanie za pomocą aplikacji <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Zezwolić aplikacji <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> na udostępnianie lub nagrywanie?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Cały ekran"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Pojedyncza aplikacja"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Dalej"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Udostępnianie i nagrywanie za pomocą aplikacji"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Zarządzaj"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 9110aa0117a4..085522ae1412 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificação persistente de uma sessão de gravação de ecrã"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Iniciar a gravação?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Enquanto estiver a gravar, o sistema Android pode capturar quaisquer informações confidenciais que estejam visíveis no ecrã ou que sejam reproduzidas no dispositivo. Isto inclui palavras-passe, informações de pagamento, fotos, mensagens e áudio."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gravar o ecrã inteiro"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Gravar só uma app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Enquanto está a gravar, o Android tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Enquanto está a gravar uma app, o Android tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Começar gravação"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Gravar áudio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Áudio do dispositivo"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"O som do dispositivo, como música, chamadas e toques."</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"O serviço que fornece esta função terá acesso a todas as informações que estiverem visíveis no ecrã ou que forem reproduzidas a partir do dispositivo durante a gravação ou transmissão. Isto inclui informações como palavras-passe, detalhes de pagamentos, fotos, mensagens e áudio reproduzido."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Começar a gravar ou a transmitir?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Começar a gravar ou a transmitir com a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Permitir que a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> partilhe ou grave?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ecrã inteiro"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Só uma app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quando está a partilhar, gravar ou transmitir, a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando está a partilhar, gravar ou transmitir uma app, a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partilhe ou grave uma app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gerir"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index f82954eb46c1..0db2dd594477 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificare în curs pentru o sesiune de înregistrare a ecranului"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Începi înregistrarea?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"În timpul înregistrării, sistemul Android poate captura informațiile sensibile vizibile pe ecran sau redate pe dispozitiv. Aici sunt incluse parole, informații de plată, fotografii, mesaje și conținut audio."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Înregistrează tot ecranul"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Înregistrează doar o aplicație"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Când înregistrezi, Android are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Când înregistrezi o aplicație, Android are acces la orice se afișează sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Începe înregistrarea"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Înregistrează audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Conținutul audio de la dispozitiv"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sunetul de la dispozitiv, precum muzică, apeluri și tonuri de sonerie"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Serviciul care oferă această funcție va avea acces la toate informațiile vizibile pe ecran sau redate pe dispozitiv în timp ce înregistrezi sau proiectezi. Între aceste informații se numără parole, detalii de plată, fotografii, mesaje și conținutul audio pe care îl redai."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Începi să înregistrezi sau să proiectezi?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Începi să înregistrezi sau să proiectezi cu <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> poate permite accesul sau înregistra?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tot ecranul"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"O singură aplicație"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Când permiți accesul, înregistrezi sau proiectezi, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> are acces la orice se afișează pe ecran sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuă"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Permite accesul la o aplicație sau înregistreaz-o"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestionează"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Istoric"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 84b5061755a5..7dcf873da40e 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущее уведомление для записи видео с экрана"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Начать запись?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"В записи может появиться конфиденциальная информация, которая видна на экране или воспроизводится на устройстве, например пароли, сведения о платежах, фотографии, сообщения и аудиозаписи."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Записывать весь экран"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Записывать окно приложения"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Когда вы записываете видео с экрана, Android получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Когда вы записываете видео с окна приложения, Android получает доступ ко всему, что видно и воспроизводится в приложении. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Начать запись"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Записывать аудио"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук с устройства"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук с вашего устройства, например музыка, звонки и рингтоны"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Во время записи или трансляции у сервиса, предоставляющего эту функцию, будет доступ ко всей информации, которая видна на экране или воспроизводится на устройстве, включая пароли, сведения о платежах, фотографии, сообщения и звуки."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Начать запись или трансляцию?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Начать запись или трансляцию через приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\"?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Разрешить приложению \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" демонстрировать экран или записывать видео с него?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Весь экран"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Отдельное приложение"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Когда вы демонстрируете, транслируете экран или записываете видео с него, приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когда вы демонстрируете, транслируете экран или записываете видео с него, приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Далее"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Демонстрация экрана или запись видео с него"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Настроить"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 1ccdbf36507f..de498cb075c8 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"තිර පටිගත කිරීමේ සැසියක් සඳහා කෙරෙන දැනුම් දීම"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"පටිගත කිරීම ආරම්භ කරන්නද?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"පටිගත කරන අතරතුර, Android පද්ධතියට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය කරන ඕනෑම සංවේදී තොරතුරක් ග්‍රහණය කර ගැනීමට හැකිය. මෙයට මුරපද, ගෙවීම් තොරතුරු, ඡායාරූප, පණිවිඩ සහ ඕඩියෝ ඇතුළත් වේ."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"සම්පූර්ණ තිරය පටිගත කරන්න"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"තනි යෙදුමක් පටිගත කරන්න"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ඔබ පටිගත කරන අතරේ, Android හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්‍රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්‍රවේශම් වන්න."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ඔබ යෙදුමක් පටිගත කරන අතරේ, Android හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්‍රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්‍රවේශම් වන්න."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"පටිගත කිරීම අරඹන්න"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ඕඩියෝ පටිගත කරන්න"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"උපාංග ඕඩියෝ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"සංගීතය, ඇමතුම් සහ නාද රිද්ම වැනි ඔබේ උපාංගය වෙතින් ශබ්ද"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"මෙම ශ්‍රිතය සපයන සේවාවට පටිගත කරන හෝ විකාශ කරන අතරතුර ඔබේ තිරයේ දිස් වන හෝ ඔබේ උපාංගයෙන් වාදනය කරන සියලු තොරතුරු වෙත ප්‍රවේශය ලැබෙනු ඇත. මෙහි මුරපද, ගෙවීම් විස්තර, ඡායාරූප, පණිවිඩ සහ ඔබ වාදනය කරන ඕඩියෝ යනාදි තොරතුරු ඇතුළත් වේ."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"පටිගත කිරීම හෝ විකාශය කිරීම ආරම්භ කරන්නද?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> සමග පටිගත කිරීම හෝ විකාශය කිරීම ආරම්භ කරන්නද?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට බෙදා ගැනීමට හෝ පටිගත කිරීමට ඉඩ දෙන්න ද?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"සම්පූර්ණ තිරය"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"තනි යෙදුමක්"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"ඔබ බෙදා ගන්නා විට, පටිගත කරන විට, හෝ විකාශනය කරන විට, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්‍රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්‍රවේශම් වන්න."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්‍රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්‍රවේශම් වන්න."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ඉදිරියට යන්න"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"යෙදුමක් බෙදා ගන්න හෝ පටිගත කරන්න"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"කළමනාකරණය කරන්න"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ඉතිහාසය"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index dcebddb4d999..c1a83db6752f 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Njoftim i vazhdueshëm për një seancë regjistrimi të ekranit"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Të niset regjistrimi?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Gjatë regjistrimit, sistemi Android mund të regjistrojë çdo informacion delikat që është i dukshëm në ekranin tënd ose që luhet në pajisje. Kjo përfshin fjalëkalimet, informacionin e pagesave, fotografitë, mesazhet dhe audion."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Regjistro të gjithë ekranin"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Regjistro vetëm një aplikacion"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Gjatë regjistrimit, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Gjatë regjistrimit të një aplikacioni, Android ka qasje te çdo gjë e dukshme ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Nis regjistrimin"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Regjistro audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audioja e pajisjes"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Tingulli nga pajisja, si muzika, telefonatat dhe tonet e ziles"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Shërbimi që e ofron këtë funksion do të ketë qasje te të gjitha informacionet që janë të dukshme në ekran ose që luhen nga pajisja jote gjatë regjistrimit ose transmetimit. Kjo përfshin informacione, si p.sh.: fjalëkalimet, detajet e pagesave, fotografitë, mesazhet dhe audion që luan ti."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Do të fillosh regjistrimin ose transmetimin?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Fillo regjistrimin ose transmetimin me <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Të lejohet <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> të shpërndajë ose regjistrojë?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ekran i plotë"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Vetëm një aplikacion"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Gjatë shpërndarjes, regjistrimit ose transmetimit, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Gjatë shpërndarjes, regjistrimit ose transmetimit të një aplikacioni, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Vazhdo"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Shpërndaj ose regjistro një aplikacion"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Menaxho"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historiku"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 77f0d30b412d..1778b01b2891 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Avisering om att skärminspelning pågår"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Vill du starta inspelningen?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"När du spelar in kan Android-systemet registrera alla känsliga uppgifter som visas på skärmen eller spelas upp på enheten. Detta omfattar lösenord, betalningsuppgifter, foton, meddelanden och ljud."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Spela in hela skärmen"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Spela in en enda app"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"När du spelar in har Android åtkomst till allt som visas på skärmen eller spelas upp på enheten. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"När du spelar in en app har Android åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Börja spela in"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Spela in ljud"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Ljud på enheten"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Ljud från enheten, till exempel musik, samtal och ringsignaler"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Den tjänst som tillhandahåller funktionen får åtkomst till all information som visas på skärmen eller spelas upp från enheten när du spelar in eller castar. Detta omfattar uppgifter som lösenord, betalningsinformation, foton, meddelanden och ljud som du spelar upp."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vill du börja spela in eller casta?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Vill du börja spela in eller casta med <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vill du tillåta att <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> delar eller spelar in?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hela skärmen"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"En enda app"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"När du delar, spelar in eller castar har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> åtkomst till allt som visas på skärmen eller spelas upp på enheten. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"När du delar, spelar in eller castar en app har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsätt"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dela eller spela in en app"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Hantera"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml
new file mode 100644
index 000000000000..8148d3dfaf7d
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="AuthCredentialPatternContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">420dp</item>
+ <item name="android:maxWidth">420dp</item>
+ <item name="android:minHeight">200dp</item>
+ <item name="android:minWidth">200dp</item>
+ <item name="android:paddingHorizontal">120dp</item>
+ <item name="android:paddingVertical">40dp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:textSize">36sp</item>
+ <item name="android:focusable">true</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Subtitle">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Description">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml
new file mode 100644
index 000000000000..771de08cb360
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="AuthCredentialHeaderStyle">
+ <item name="android:paddingStart">120dp</item>
+ <item name="android:paddingEnd">120dp</item>
+ <item name="android:paddingTop">80dp</item>
+ <item name="android:paddingBottom">10dp</item>
+ <item name="android:layout_gravity">top</item>
+ </style>
+
+ <style name="AuthCredentialPatternContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">420dp</item>
+ <item name="android:maxWidth">420dp</item>
+ <item name="android:minHeight">200dp</item>
+ <item name="android:minWidth">200dp</item>
+ <item name="android:paddingHorizontal">180dp</item>
+ <item name="android:paddingVertical">80dp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">24dp</item>
+ <item name="android:textSize">36sp</item>
+ <item name="android:focusable">true</item>
+ </style>
+
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml
new file mode 100644
index 000000000000..f9ed67d89de7
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="AuthCredentialPatternContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">420dp</item>
+ <item name="android:maxWidth">420dp</item>
+ <item name="android:minHeight">200dp</item>
+ <item name="android:minWidth">200dp</item>
+ <item name="android:paddingHorizontal">120dp</item>
+ <item name="android:paddingVertical">40dp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:textSize">36sp</item>
+ <item name="android:focusable">true</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Subtitle">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Description">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml
new file mode 100644
index 000000000000..78d299c483e6
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="AuthCredentialHeaderStyle">
+ <item name="android:paddingStart">120dp</item>
+ <item name="android:paddingEnd">120dp</item>
+ <item name="android:paddingTop">80dp</item>
+ <item name="android:paddingBottom">10dp</item>
+ <item name="android:layout_gravity">top</item>
+ </style>
+
+ <style name="AuthCredentialPatternContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">420dp</item>
+ <item name="android:maxWidth">420dp</item>
+ <item name="android:minHeight">200dp</item>
+ <item name="android:minWidth">200dp</item>
+ <item name="android:paddingHorizontal">240dp</item>
+ <item name="android:paddingVertical">120dp</item>
+ </style>
+
+ <style name="TextAppearance.AuthNonBioCredential.Title">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:layout_marginTop">24dp</item>
+ <item name="android:textSize">36sp</item>
+ <item name="android:focusable">true</item>
+ </style>
+
+</resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index c823f04238cb..dbb90b3c6761 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"திரை ரெக்கார்டிங் அமர்விற்கான தொடர் அறிவிப்பு"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ரெக்கார்டிங்கைத் தொடங்கவா?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"ரெக்கார்டு செய்யும்போது, உங்கள் திரையில் தோன்றக்கூடிய அல்லது சாதனத்தில் பிளே ஆகக்கூடிய பாதுகாக்கப்பட வேண்டிய தகவலை Android சிஸ்டம் படமெடுக்க முடியும். கடவுச்சொற்கள், பேமெண்ட் தகவல், படங்கள், மெசேஜ்கள், ஆடியோ ஆகியவை இதில் அடங்கும்."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"முழு திரையை ரெக்கார்டு செய்தல்"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ஓர் ஆப்ஸை ரெக்கார்டு செய்தல்"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"நீங்கள் ரெக்கார்டு செய்யும்போது அந்தச் சாதனத்தில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"ஓர் ஆப்ஸை நீங்கள் ரெக்கார்டு செய்யும்போது அந்த ஆப்ஸில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ரெக்கார்டிங்கைத் தொடங்கு"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"ஆடியோவை ரெக்கார்டு செய்"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"சாதன ஆடியோ"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"இசை, அழைப்புகள், ரிங்டோன்கள் போன்ற உங்கள் சாதனத்திலிருந்து வரும் ஒலி"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"இந்தச் செயல்பாட்டை வழங்கும் சேவையானது உங்கள் திரையில் தெரியும் தகவல்கள், ரெக்கார்டு செய்யும்போதோ அனுப்பும்போதோ உங்கள் சாதனத்திலிருந்து பிளே ஆகும் அனைத்துத் தகவல்கள் ஆகியவற்றுக்கான அணுகலைக் கொண்டிருக்கும். கடவுச்சொற்கள், பேமெண்ட் தொடர்பான தகவல்கள், படங்கள், மெசேஜ்கள், நீங்கள் பிளே செய்யும் ஆடியோ போன்ற அனைத்துத் தகவல்களும் இதில் அடங்கும்."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ரெக்கார்டிங் செய்யவோ அனுப்புவோ தொடங்கவா?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> மூலம் ரெக்கார்டிங் செய்யவோ அனுப்புவதற்கோ தொடங்கிவீட்டீர்களா?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"பகிர அல்லது ரெக்கார்டு செய்ய <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸை அனுமதிக்கலாமா?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"திரை முழுவதும்"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ஓர் ஆப்ஸ்"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ உங்கள் சாதனத்தில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ஓர் ஆப்ஸை நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அந்த ஆப்ஸில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"தொடர்க"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ஆப்ஸைப் பகிர்தல் அல்லது ரெக்கார்டு செய்தல்"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"நிர்வகி"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"இதுவரை வந்த அறிவிப்புகள்"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index e911bb2fbb16..a238f3f64300 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekran kaydı oturumu için devam eden bildirim"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Kayıt başlatılsın mı?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Kayıt sırasında Android Sistemi, ekranınızda görünen veya cihazınızda oynatılan hassas bilgileri yakalayabilir. Buna şifreler, ödeme bilgileri, fotoğraflar, mesajlar ve sesler dahildir."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Tüm ekranı kaydedin"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Tek bir uygulamayı kaydedin"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Kayıt özelliğini kullandığınızda Android, ekranınızda görünen veya cihazınızda oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Bir uygulamayı kaydetme özelliğini kullandığınızda Android, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Kaydı başlat"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Ses kaydet"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Cihaz sesi"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Müzik, aramalar, zil sesleri gibi cihazınızdan sesler"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Bu işlevi sağlayan hizmet, ekranınızda görünen veya kayıt ya da yayın sırasında cihazınızdan oynatılan tüm bilgilere erişecektir. Bu bilgiler arasında şifreler, ödeme detayları, fotoğraflar, mesajlar ve çaldığınız sesler gibi bilgiler yer alır."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Kayıt veya yayınlama başlatılsın mı?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ile kayıt veya yayınlama başlatılsın mı?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> uygulamasının paylaşmasına veya kaydetmesine izin verilsin mi?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tüm ekran"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Tek bir uygulama"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Paylaşım, kayıt ve yayınlama özelliklerini kullandığınızda <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, ekranınızda görünen veya cihazınızda oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Bir uygulamayı paylaşma, kaydetme ve yayınlama özelliklerini kullandığınızda <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Devam"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Uygulamayı paylaşın veya kaydedin"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Yönet"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Geçmiş"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 011c8bc6e901..d3f392ae7a26 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Сповіщення про сеанс запису екрана"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Почати запис?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"Під час запису система Android може фіксувати будь-яку конфіденційну інформацію, яка з\'являється на екрані або відтворюється на пристрої, зокрема паролі, платіжну інформацію, фотографії, повідомлення та звуки."</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Записувати весь екран"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Записувати окремий додаток"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Коли ви записуєте вміст екрана, ОС Android отримує доступ до всього, що відображається на ньому або відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Коли ви записуєте додаток, ОС Android отримує доступ до всього, що відображається або відтворюється в цьому додатку. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"Почати запис"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Записувати звук"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук із пристрою"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук із пристрою, зокрема музика, виклики та сигнали дзвінка"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Сервіс, що надає цю функцію, матиме доступ до всієї інформації, яка з\'являється на екрані або відтворюється на пристрої під час запису чи трансляції, зокрема до паролів, інформації про платежі, фотографій, повідомлень і аудіофайлів."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Почати запис або трансляцію?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Почати запис або трансляцію за допомогою додатка <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Дозволити додатку <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> показувати або записувати?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Увесь екран"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Окремий додаток"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Коли ви показуєте, записуєте або транслюєте екран, додаток <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> отримує доступ до всього, що відображається на екрані чи відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Коли ви показуєте, записуєте або транслюєте додаток, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> отримує доступ до всього, що відображається або відтворюється в цьому додатку. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Продовжити"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Показувати або записувати додаток"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Керувати"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Історія"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 7d0df6c3e3d3..1e71b14ac717 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"اسکرین ریکارڈ سیشن کیلئے جاری اطلاع"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"ریکارڈنگ شروع کریں؟"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"‏ریکارڈ کرنے کے دوران، Android سسٹم آپ کی اسکرین پر نظر آنے والی یا آپ کے آلہ پر چلنے والی کسی بھی حساس معلومات کو کیپچر کر سکتا ہے۔ اس میں پاس ورڈز، ادائیگی کی معلومات، تصاویر، پیغامات اور آڈیو شامل ہیں۔"</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"پوری اسکرین کو ریکارڈ کریں"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"واحد ایپ کو ریکارڈ کریں"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"‏جب آپ ریکارڈنگ کر رہے ہوتے ہیں تو Android کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"‏جب آپ کسی ایپ کو ریکارڈ کر رہے ہوتے ہیں تو Android کو اس ایپ پر دکھائی گئی یا چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"ریکارڈنگ شروع کریں"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"آڈیو ریکارڈ کریں"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"آلہ کا آڈیو"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"آپ کے آلے سے آواز، جیسے موسیقی، کالز اور رِنگ ٹونز"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"یہ فنکشن فراہم کرنے والی سروس کو اس تمام معلومات تک رسائی حاصل ہوگی جو آپ کی اسکرین پر نظر آتی ہے یا ریکارڈنگ یا کاسٹنگ کے دوران آپ کے آلے سے چلائی جاتی ہے۔ اس میں پاس ورڈز، ادائیگی کی تفصیلات، تصاویر، پیغامات اور وہ آڈیو جو آپ چلاتے ہیں جیسی معلومات شامل ہے۔"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ریکارڈنگ یا کاسٹنگ شروع کریں؟"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کے ذریعے ریکارڈنگ یا کاسٹنگ شروع کریں؟"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو اشتراک یا ریکارڈ کرنے کی اجازت دیں؟"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"پوری اسکرین"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"واحد ایپ"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو آپ کی اسکرین پر دکھائی گئی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"جاری رکھیں"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ایپ کا اشتراک یا ریکارڈ کریں"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"نظم کریں"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"سرگزشت"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index d881fa20644c..87821bffc087 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -94,16 +94,11 @@
<string name="screenrecord_channel_description" msgid="4147077128486138351">"持续显示屏幕录制会话通知"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"要开始录制吗?"</string>
<string name="screenrecord_description" msgid="1123231719680353736">"在录制内容时,Android 系统可以捕捉到您屏幕上显示或设备中播放的敏感信息,其中包括密码、付款信息、照片、消息和音频。"</string>
- <!-- no translation found for screenrecord_option_entire_screen (1732437834603426934) -->
- <skip />
- <!-- no translation found for screenrecord_option_single_app (5954863081500035825) -->
- <skip />
- <!-- no translation found for screenrecord_warning_entire_screen (8141407178104195610) -->
- <skip />
- <!-- no translation found for screenrecord_warning_single_app (7760723997065948283) -->
- <skip />
- <!-- no translation found for screenrecord_start_recording (348286842544768740) -->
- <skip />
+ <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"录制整个屏幕"</string>
+ <string name="screenrecord_option_single_app" msgid="5954863081500035825">"录制单个应用"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"在您进行录制时,Android 可以访问您的屏幕显示或设备播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"在您录制某个应用时,Android 可以访问此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+ <string name="screenrecord_start_recording" msgid="348286842544768740">"开始录制"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"录制音频"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"设备音频"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"设备发出的声音,例如音乐、通话和铃声"</string>
@@ -371,20 +366,13 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"在录制或投放内容时,提供此功能的服务将可获取您屏幕上显示或设备中播放的所有信息,其中包括密码、付款明细、照片、消息以及您播放的音频等信息。"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要开始录制或投放内容吗?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"要开始使用<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>录制或投放内容吗?"</string>
- <!-- no translation found for media_projection_permission_dialog_title (7130975432309482596) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_entire_screen (392086473225692983) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_option_single_app (1591110238124910521) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_entire_screen (3989078820637452717) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_warning_single_app (1659532781536753059) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_continue (1827799658916736006) -->
- <skip />
- <!-- no translation found for media_projection_permission_app_selector_title (894251621057480704) -->
- <skip />
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允许 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或录制吗?"</string>
+ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"整个屏幕"</string>
+ <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"单个应用"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问您的屏幕显示或设备播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问通过此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+ <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"继续"</string>
+ <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或录制应用"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"历史记录"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index d934786b4551..c7036192b418 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -96,8 +96,8 @@
<string name="screenrecord_description" msgid="1123231719680353736">"錄影時,Android 系統可擷取螢幕上顯示或裝置播放的任何敏感資料,包括密碼、付款資料、相片、訊息和音訊。"</string>
<string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"錄製整個螢幕畫面"</string>
<string name="screenrecord_option_single_app" msgid="5954863081500035825">"錄製單一應用程式"</string>
- <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"進行錄製時,Android 可以存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
- <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"錄製應用程式時,Android 可以存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"進行錄製時,Android 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"錄製應用程式時,Android 可存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
<string name="screenrecord_start_recording" msgid="348286842544768740">"開始錄製"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"錄音"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"裝置音訊"</string>
@@ -366,11 +366,11 @@
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"在錄影或投放時,此功能的服務供應商可以存取螢幕顯示或裝置播放的任何資料,當中包括密碼、付款詳情、相片、訊息和播放的語音等。"</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"要開始錄影或投放嗎?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"要使用「<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>」開始錄影或投放嗎?"</string>
- <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允許 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或錄製?"</string>
+ <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允許 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或錄製嗎?"</string>
<string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"整個螢幕畫面"</string>
<string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"單一應用程式"</string>
- <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
- <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放應用程式時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index bbe4b6c70c1c..f5fc7ee53f2e 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -672,7 +672,7 @@
<string name="data_connection_no_internet" msgid="691058178914184544">"沒有網際網路連線"</string>
<string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"開啟「<xliff:g id="ID_1">%s</xliff:g>」設定。"</string>
<string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"編輯設定順序。"</string>
- <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源按鈕選單"</string>
+ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源鍵選單"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"第 <xliff:g id="ID_1">%1$d</xliff:g> 頁,共 <xliff:g id="ID_2">%2$d</xliff:g> 頁"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"鎖定畫面"</string>
<string name="thermal_shutdown_title" msgid="2702966892682930264">"手機先前過熱,因此關閉電源"</string>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 9a71995383ac..df0659d67afe 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -191,5 +191,18 @@
<declare-styleable name="DelayableMarqueeTextView">
<attr name="marqueeDelay" format="integer" />
</declare-styleable>
+
+ <declare-styleable name="AuthCredentialView">
+ <attr name="lockPatternStyle" format="reference" />
+ <attr name="lockPinPasswordStyle" format="reference" />
+ <attr name="containerStyle" format="reference" />
+ <attr name="headerStyle" format="reference" />
+ <attr name="headerIconStyle" format="reference" />
+ <attr name="titleTextAppearance" format="reference" />
+ <attr name="subTitleTextAppearance" format="reference" />
+ <attr name="descriptionTextAppearance" format="reference" />
+ <attr name="passwordTextAppearance" format="reference" />
+ <attr name="errorTextAppearance" format="reference"/>
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml
index c67ac8d34aa6..8221d78fbfd7 100644
--- a/packages/SystemUI/res/values/bools.xml
+++ b/packages/SystemUI/res/values/bools.xml
@@ -18,6 +18,13 @@
<resources>
<!-- Whether to show the user switcher in quick settings when only a single user is present. -->
<bool name="qs_show_user_switcher_for_single_user">false</bool>
+
<!-- Whether to show a custom biometric prompt size-->
<bool name="use_custom_bp_size">false</bool>
+
+ <!-- Whether to enable clipping on Quick Settings -->
+ <bool name="qs_enable_clipping">true</bool>
+
+ <!-- Whether to enable transparent background for notification scrims -->
+ <bool name="notification_scrim_transparent">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2eebdc61186c..93926ef9e780 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -519,7 +519,7 @@
<dimen name="qs_tile_margin_horizontal">8dp</dimen>
<dimen name="qs_tile_margin_vertical">@dimen/qs_tile_margin_horizontal</dimen>
<dimen name="qs_tile_margin_top_bottom">4dp</dimen>
- <dimen name="qs_brightness_margin_top">8dp</dimen>
+ <dimen name="qs_brightness_margin_top">12dp</dimen>
<dimen name="qs_brightness_margin_bottom">16dp</dimen>
<dimen name="qqs_layout_margin_top">16dp</dimen>
<dimen name="qqs_layout_padding_bottom">24dp</dimen>
@@ -572,6 +572,7 @@
<dimen name="qs_header_row_min_height">48dp</dimen>
<dimen name="qs_header_non_clickable_element_height">24dp</dimen>
+ <dimen name="new_qs_header_non_clickable_element_height">20dp</dimen>
<dimen name="qs_footer_padding">20dp</dimen>
<dimen name="qs_security_footer_height">88dp</dimen>
@@ -949,6 +950,9 @@
<dimen name="biometric_dialog_width">240dp</dimen>
<dimen name="biometric_dialog_height">240dp</dimen>
+ <!-- Biometric Auth Credential values -->
+ <dimen name="biometric_auth_icon_size">48dp</dimen>
+
<!-- Starting text size in sp of batteryLevel for wireless charging animation -->
<item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">
0
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index 3164ed1e6751..e30d4415a0c4 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -28,4 +28,11 @@
<!-- The time it takes for the over scroll release animation to complete, in milli seconds. -->
<integer name="lockscreen_shade_over_scroll_release_duration">0</integer>
+
+ <!-- Values for transition of QS Headers -->
+ <integer name="fade_out_complete_frame">14</integer>
+ <integer name="fade_in_start_frame">58</integer>
+ <!-- Percentage of displacement for items in QQS to guarantee matching with bottom of clock at
+ fade_out_complete_frame -->
+ <dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9b9111fcf9a8..212c77b50477 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -641,7 +641,7 @@
<!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] -->
<string name="quick_settings_color_correction_label">Color correction</string>
<!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] -->
- <string name="quick_settings_more_user_settings">User settings</string>
+ <string name="quick_settings_more_user_settings">Manage users</string>
<!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
<string name="quick_settings_done">Done</string>
<!-- QuickSettings: Control panel: Label for button that dismisses user switcher control panel. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ac3eb7e18539..e76887babc50 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -128,11 +128,10 @@
<!-- This is hard coded to be sans-serif-condensed to match the icons -->
<style name="TextAppearance.QS.Status">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">14sp</item>
<item name="android:letterSpacing">0.01</item>
- <item name="android:lineHeight">20sp</item>
</style>
<style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status">
@@ -143,12 +142,10 @@
<style name="TextAppearance.QS.Status.Carriers" />
<style name="TextAppearance.QS.Status.Carriers.NoCarrierText">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="TextAppearance.QS.Status.Build">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
@@ -200,8 +197,9 @@
<style name="TextAppearance.AuthNonBioCredential.Title">
<item name="android:fontFamily">google-sans</item>
- <item name="android:layout_marginTop">20dp</item>
- <item name="android:textSize">36sp</item>
+ <item name="android:layout_marginTop">24dp</item>
+ <item name="android:textSize">36dp</item>
+ <item name="android:focusable">true</item>
</style>
<style name="TextAppearance.AuthNonBioCredential.Subtitle">
@@ -213,12 +211,10 @@
<style name="TextAppearance.AuthNonBioCredential.Description">
<item name="android:fontFamily">google-sans</item>
<item name="android:layout_marginTop">20dp</item>
- <item name="android:textSize">16sp</item>
+ <item name="android:textSize">18sp</item>
</style>
<style name="TextAppearance.AuthNonBioCredential.Error">
- <item name="android:paddingTop">6dp</item>
- <item name="android:paddingBottom">18dp</item>
<item name="android:paddingHorizontal">24dp</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/colorError</item>
@@ -227,20 +223,43 @@
<style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:gravity">center</item>
+ <item name="android:paddingTop">28dp</item>
<item name="android:singleLine">true</item>
<item name="android:textColor">?android:attr/colorForeground</item>
<item name="android:textSize">24sp</item>
+ <item name="android:background">@drawable/edit_text_filled</item>
</style>
<style name="AuthCredentialHeaderStyle">
<item name="android:paddingStart">48dp</item>
- <item name="android:paddingEnd">24dp</item>
- <item name="android:paddingTop">28dp</item>
- <item name="android:paddingBottom">20dp</item>
- <item name="android:orientation">vertical</item>
+ <item name="android:paddingEnd">48dp</item>
+ <item name="android:paddingTop">48dp</item>
+ <item name="android:paddingBottom">10dp</item>
<item name="android:layout_gravity">top</item>
</style>
+ <style name="AuthCredentialIconStyle">
+ <item name="android:layout_width">@dimen/biometric_auth_icon_size</item>
+ <item name="android:layout_height">@dimen/biometric_auth_icon_size</item>
+ </style>
+
+ <style name="AuthCredentialPatternContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">420dp</item>
+ <item name="android:maxWidth">420dp</item>
+ <item name="android:minHeight">200dp</item>
+ <item name="android:minWidth">200dp</item>
+ <item name="android:padding">20dp</item>
+ </style>
+
+ <style name="AuthCredentialPinPasswordContainerStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:maxHeight">48dp</item>
+ <item name="android:maxWidth">600dp</item>
+ <item name="android:minHeight">48dp</item>
+ <item name="android:minWidth">200dp</item>
+ </style>
+
<style name="DeviceManagementDialogTitle">
<item name="android:gravity">center</item>
<item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item>
@@ -278,7 +297,9 @@
<item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
<item name="wallpaperTextColorAccent">@color/material_dynamic_primary90</item>
<item name="android:colorError">@*android:color/error_color_material_dark</item>
- <item name="*android:lockPatternStyle">@style/LockPatternStyle</item>
+ <item name="*android:lockPatternStyle">@style/LockPatternViewStyle</item>
+ <item name="lockPatternStyle">@style/LockPatternContainerStyle</item>
+ <item name="lockPinPasswordStyle">@style/LockPinPasswordContainerStyle</item>
<item name="passwordStyle">@style/PasswordTheme</item>
<item name="numPadKeyStyle">@style/NumPadKey</item>
<item name="backgroundProtectedStyle">@style/BackgroundProtectedStyle</item>
@@ -304,27 +325,33 @@
<item name="android:textColor">?attr/wallpaperTextColor</item>
</style>
- <style name="LockPatternContainerStyle">
- <item name="android:maxHeight">400dp</item>
- <item name="android:maxWidth">420dp</item>
- <item name="android:minHeight">0dp</item>
- <item name="android:minWidth">0dp</item>
- <item name="android:paddingHorizontal">60dp</item>
- <item name="android:paddingBottom">40dp</item>
+ <style name="AuthCredentialStyle">
+ <item name="*android:regularColor">?android:attr/colorForeground</item>
+ <item name="*android:successColor">?android:attr/colorForeground</item>
+ <item name="*android:errorColor">?android:attr/colorError</item>
+ <item name="*android:dotColor">?android:attr/textColorSecondary</item>
+ <item name="headerStyle">@style/AuthCredentialHeaderStyle</item>
+ <item name="headerIconStyle">@style/AuthCredentialIconStyle</item>
+ <item name="titleTextAppearance">@style/TextAppearance.AuthNonBioCredential.Title</item>
+ <item name="subTitleTextAppearance">@style/TextAppearance.AuthNonBioCredential.Subtitle</item>
+ <item name="descriptionTextAppearance">@style/TextAppearance.AuthNonBioCredential.Description</item>
+ <item name="passwordTextAppearance">@style/TextAppearance.AuthCredential.PasswordEntry</item>
+ <item name="errorTextAppearance">@style/TextAppearance.AuthNonBioCredential.Error</item>
</style>
- <style name="LockPatternStyle">
+ <style name="LockPatternViewStyle" >
<item name="*android:regularColor">?android:attr/colorAccent</item>
<item name="*android:successColor">?android:attr/textColorPrimary</item>
<item name="*android:errorColor">?android:attr/colorError</item>
<item name="*android:dotColor">?android:attr/textColorSecondary</item>
</style>
- <style name="LockPatternStyleBiometricPrompt">
- <item name="*android:regularColor">?android:attr/colorForeground</item>
- <item name="*android:successColor">?android:attr/colorForeground</item>
- <item name="*android:errorColor">?android:attr/colorError</item>
- <item name="*android:dotColor">?android:attr/textColorSecondary</item>
+ <style name="LockPatternContainerStyle" parent="@style/AuthCredentialStyle">
+ <item name="containerStyle">@style/AuthCredentialPatternContainerStyle</item>
+ </style>
+
+ <style name="LockPinPasswordContainerStyle" parent="@style/AuthCredentialStyle">
+ <item name="containerStyle">@style/AuthCredentialPinPasswordContainerStyle</item>
</style>
<style name="Theme.SystemUI.QuickSettings" parent="@*android:style/Theme.DeviceDefault">
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index f3866c08cbfc..de855e275f5f 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -27,67 +27,60 @@
<KeyPosition
app:keyPositionType="deltaRelative"
app:percentX="0"
- app:percentY="0"
- app:framePosition="49"
+ app:percentY="@dimen/percent_displacement_at_fade_out"
+ app:framePosition="@integer/fade_out_complete_frame"
app:sizePercent="0"
app:curveFit="linear"
app:motionTarget="@id/date" />
<KeyPosition
app:keyPositionType="deltaRelative"
app:percentX="1"
- app:percentY="0.51"
+ app:percentY="0.5"
app:sizePercent="1"
- app:framePosition="51"
+ app:framePosition="50"
app:curveFit="linear"
app:motionTarget="@id/date" />
<KeyAttribute
app:motionTarget="@id/date"
- app:framePosition="30"
+ app:framePosition="14"
android:alpha="0"
/>
<KeyAttribute
app:motionTarget="@id/date"
- app:framePosition="70"
+ app:framePosition="@integer/fade_in_start_frame"
android:alpha="0"
/>
<KeyPosition
- app:keyPositionType="pathRelative"
- app:percentX="0"
- app:percentY="0"
- app:framePosition="0"
- app:curveFit="linear"
- app:motionTarget="@id/statusIcons" />
- <KeyPosition
- app:keyPositionType="pathRelative"
+ app:keyPositionType="deltaRelative"
app:percentX="0"
- app:percentY="0"
- app:framePosition="50"
+ app:percentY="@dimen/percent_displacement_at_fade_out"
+ app:framePosition="@integer/fade_out_complete_frame"
app:sizePercent="0"
app:curveFit="linear"
app:motionTarget="@id/statusIcons" />
<KeyPosition
app:keyPositionType="deltaRelative"
app:percentX="1"
- app:percentY="0.51"
- app:framePosition="51"
+ app:percentY="0.5"
+ app:framePosition="50"
app:sizePercent="1"
app:curveFit="linear"
app:motionTarget="@id/statusIcons" />
<KeyAttribute
app:motionTarget="@id/statusIcons"
- app:framePosition="30"
+ app:framePosition="@integer/fade_out_complete_frame"
android:alpha="0"
/>
<KeyAttribute
app:motionTarget="@id/statusIcons"
- app:framePosition="70"
+ app:framePosition="@integer/fade_in_start_frame"
android:alpha="0"
/>
<KeyPosition
app:keyPositionType="deltaRelative"
app:percentX="0"
- app:percentY="0"
- app:framePosition="50"
+ app:percentY="@dimen/percent_displacement_at_fade_out"
+ app:framePosition="@integer/fade_out_complete_frame"
app:percentWidth="1"
app:percentHeight="1"
app:curveFit="linear"
@@ -95,27 +88,27 @@
<KeyPosition
app:keyPositionType="deltaRelative"
app:percentX="1"
- app:percentY="0.51"
- app:framePosition="51"
+ app:percentY="0.5"
+ app:framePosition="50"
app:percentWidth="1"
app:percentHeight="1"
app:curveFit="linear"
app:motionTarget="@id/batteryRemainingIcon" />
<KeyAttribute
app:motionTarget="@id/batteryRemainingIcon"
- app:framePosition="30"
+ app:framePosition="@integer/fade_out_complete_frame"
android:alpha="0"
/>
<KeyAttribute
app:motionTarget="@id/batteryRemainingIcon"
- app:framePosition="70"
+ app:framePosition="@integer/fade_in_start_frame"
android:alpha="0"
/>
<KeyPosition
app:motionTarget="@id/carrier_group"
app:percentX="1"
- app:percentY="0.51"
- app:framePosition="51"
+ app:percentY="0.5"
+ app:framePosition="50"
app:percentWidth="1"
app:percentHeight="1"
app:curveFit="linear"
@@ -126,7 +119,7 @@
android:alpha="0" />
<KeyAttribute
app:motionTarget="@id/carrier_group"
- app:framePosition="70"
+ app:framePosition="@integer/fade_in_start_frame"
android:alpha="0" />
</KeyFrameSet>
</Transition>
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index a82684d0358b..88b4f43b440b 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -43,7 +43,8 @@
android:id="@+id/date">
<Layout
android:layout_width="0dp"
- android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ android:layout_marginStart="8dp"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/clock"
app:layout_constraintEnd_toStartOf="@id/barrier"
@@ -57,8 +58,8 @@
android:id="@+id/statusIcons">
<Layout
android:layout_width="0dp"
- android:layout_height="@dimen/qs_header_non_clickable_element_height"
- app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
app:layout_constraintTop_toTopOf="parent"
@@ -71,9 +72,9 @@
android:id="@+id/batteryRemainingIcon">
<Layout
android:layout_width="wrap_content"
- android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constrainedWidth="true"
- app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="@id/end_guide"
app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
index f39e6bd65b86..d8a4e7752960 100644
--- a/packages/SystemUI/res/xml/qs_header_new.xml
+++ b/packages/SystemUI/res/xml/qs_header_new.xml
@@ -40,13 +40,13 @@
android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/privacy_container"
- app:layout_constraintBottom_toTopOf="@id/date"
+ app:layout_constraintBottom_toBottomOf="@id/carrier_group"
app:layout_constraintEnd_toStartOf="@id/carrier_group"
app:layout_constraintHorizontal_bias="0"
/>
<Transform
- android:scaleX="2.4"
- android:scaleY="2.4"
+ android:scaleX="2.57"
+ android:scaleY="2.57"
/>
</Constraint>
@@ -54,11 +54,11 @@
android:id="@+id/date">
<Layout
android:layout_width="0dp"
- android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/space"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/clock"
+ app:layout_constraintTop_toBottomOf="@id/carrier_group"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
@@ -87,7 +87,7 @@
android:id="@+id/statusIcons">
<Layout
android:layout_width="0dp"
- android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/space"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
@@ -101,8 +101,8 @@
android:id="@+id/batteryRemainingIcon">
<Layout
android:layout_width="wrap_content"
- android:layout_height="@dimen/qs_header_non_clickable_element_height"
- app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/date"
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
index 2e391c7aacbe..49cc48321d77 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
@@ -19,6 +19,7 @@ package com.android.systemui.testing.screenshot
import android.app.Activity
import android.graphics.Color
import android.view.View
+import android.view.Window
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
@@ -51,13 +52,14 @@ class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestR
/**
* Compare the content of the [view] with the golden image identified by [goldenIdentifier] in
- * the context of [emulationSpec].
+ * the context of [emulationSpec]. Window must be specified to capture views that render
+ * hardware buffers.
*/
- fun screenshotTest(goldenIdentifier: String, view: View) {
+ fun screenshotTest(goldenIdentifier: String, view: View, window: Window? = null) {
view.removeElevationRecursively()
ScreenshotRuleAsserter.Builder(screenshotRule)
- .setScreenshotProvider { view.toBitmap() }
+ .setScreenshotProvider { view.toBitmap(window) }
.withMatcher(matcher)
.build()
.assertGoldenImage(goldenIdentifier)
@@ -94,6 +96,6 @@ class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestR
activity.currentFocus?.clearFocus()
}
- screenshotTest(goldenIdentifier, rootView)
+ screenshotTest(goldenIdentifier, rootView, activity.window)
}
}
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 18bd6b42386a..91fd6a60d0df 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -59,7 +59,9 @@ android_library {
resource_dirs: [
"res",
],
- java_version: "1.8",
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ },
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
}
diff --git a/packages/SystemUI/shared/proguard.flags b/packages/SystemUI/shared/proguard.flags
new file mode 100644
index 000000000000..5eda04500190
--- /dev/null
+++ b/packages/SystemUI/shared/proguard.flags
@@ -0,0 +1,4 @@
+# Retain signatures of TypeToken and its subclasses for gson usage in ClockRegistry
+-keepattributes Signature
+-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
+-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken \ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 860a5da44088..236aa669eaa9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -20,25 +20,28 @@ import android.annotation.ColorInt
import android.annotation.FloatRange
import android.annotation.IntRange
import android.annotation.SuppressLint
-import android.app.compat.ChangeIdStateCache.invalidate
import android.content.Context
import android.graphics.Canvas
+import android.graphics.Rect
import android.text.Layout
import android.text.TextUtils
import android.text.format.DateFormat
import android.util.AttributeSet
+import android.util.MathUtils
import android.widget.TextView
-import com.android.internal.R.attr.contentDescription
-import com.android.internal.R.attr.format
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.TextAnimator
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.shared.R
import java.io.PrintWriter
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
+import kotlin.math.max
+import kotlin.math.min
/**
* Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
@@ -51,14 +54,8 @@ class AnimatableClockView @JvmOverloads constructor(
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
-
- private var lastMeasureCall: CharSequence? = null
- private var lastDraw: CharSequence? = null
- private var lastTextUpdate: CharSequence? = null
- private var lastOnTextChanged: CharSequence? = null
- private var lastInvalidate: CharSequence? = null
- private var lastTimeZoneChange: CharSequence? = null
- private var lastAnimationCall: CharSequence? = null
+ var tag: String = "UnnamedClockView"
+ var logBuffer: LogBuffer? = null
private val time = Calendar.getInstance()
@@ -135,6 +132,7 @@ class AnimatableClockView @JvmOverloads constructor(
override fun onAttachedToWindow() {
super.onAttachedToWindow()
+ logBuffer?.log(tag, DEBUG, "onAttachedToWindow")
refreshFormat()
}
@@ -150,27 +148,39 @@ class AnimatableClockView @JvmOverloads constructor(
time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
contentDescription = DateFormat.format(descFormat, time)
val formattedText = DateFormat.format(format, time)
+ logBuffer?.log(tag, DEBUG,
+ { str1 = formattedText?.toString() },
+ { "refreshTime: new formattedText=$str1" }
+ )
// Setting text actually triggers a layout pass (because the text view is set to
// wrap_content width and TextView always relayouts for this). Avoid needless
// relayout if the text didn't actually change.
if (!TextUtils.equals(text, formattedText)) {
text = formattedText
+ logBuffer?.log(tag, DEBUG,
+ { str1 = formattedText?.toString() },
+ { "refreshTime: done setting new time text to: $str1" }
+ )
// Because the TextLayout may mutate under the hood as a result of the new text, we
// notify the TextAnimator that it may have changed and request a measure/layout. A
// crash will occur on the next invocation of setTextStyle if the layout is mutated
// without being notified TextInterpolator being notified.
if (layout != null) {
textAnimator?.updateLayout(layout)
+ logBuffer?.log(tag, DEBUG, "refreshTime: done updating textAnimator layout")
}
requestLayout()
- lastTextUpdate = getTimestamp()
+ logBuffer?.log(tag, DEBUG, "refreshTime: after requestLayout")
}
}
fun onTimeZoneChanged(timeZone: TimeZone?) {
time.timeZone = timeZone
refreshFormat()
- lastTimeZoneChange = "${getTimestamp()} timeZone=${time.timeZone}"
+ logBuffer?.log(tag, DEBUG,
+ { str1 = timeZone?.toString() },
+ { "onTimeZoneChanged newTimeZone=$str1" }
+ )
}
@SuppressLint("DrawAllocation")
@@ -184,22 +194,24 @@ class AnimatableClockView @JvmOverloads constructor(
} else {
animator.updateLayout(layout)
}
- lastMeasureCall = getTimestamp()
+ logBuffer?.log(tag, DEBUG, "onMeasure")
}
override fun onDraw(canvas: Canvas) {
- lastDraw = getTimestamp()
- // intentionally doesn't call super.onDraw here or else the text will be rendered twice
- textAnimator?.draw(canvas)
+ // Use textAnimator to render text if animation is enabled.
+ // Otherwise default to using standard draw functions.
+ if (isAnimationEnabled) {
+ // intentionally doesn't call super.onDraw here or else the text will be rendered twice
+ textAnimator?.draw(canvas)
+ } else {
+ super.onDraw(canvas)
+ }
+ logBuffer?.log(tag, DEBUG, "onDraw lastDraw")
}
override fun invalidate() {
super.invalidate()
- lastInvalidate = getTimestamp()
- }
-
- private fun getTimestamp(): CharSequence {
- return "${DateFormat.format("HH:mm:ss", System.currentTimeMillis())} text=$text"
+ logBuffer?.log(tag, DEBUG, "invalidate")
}
override fun onTextChanged(
@@ -209,7 +221,10 @@ class AnimatableClockView @JvmOverloads constructor(
lengthAfter: Int
) {
super.onTextChanged(text, start, lengthBefore, lengthAfter)
- lastOnTextChanged = "${getTimestamp()}"
+ logBuffer?.log(tag, DEBUG,
+ { str1 = text.toString() },
+ { "onTextChanged text=$str1" }
+ )
}
fun setLineSpacingScale(scale: Float) {
@@ -223,7 +238,7 @@ class AnimatableClockView @JvmOverloads constructor(
}
fun animateAppearOnLockscreen() {
- lastAnimationCall = "${getTimestamp()} call=animateAppearOnLockscreen"
+ logBuffer?.log(tag, DEBUG, "animateAppearOnLockscreen")
setTextStyle(
weight = dozingWeight,
textSize = -1f,
@@ -248,7 +263,7 @@ class AnimatableClockView @JvmOverloads constructor(
if (isAnimationEnabled && textAnimator == null) {
return
}
- lastAnimationCall = "${getTimestamp()} call=animateFoldAppear"
+ logBuffer?.log(tag, DEBUG, "animateFoldAppear")
setTextStyle(
weight = lockScreenWeightInternal,
textSize = -1f,
@@ -275,7 +290,7 @@ class AnimatableClockView @JvmOverloads constructor(
// Skip charge animation if dozing animation is already playing.
return
}
- lastAnimationCall = "${getTimestamp()} call=animateCharge"
+ logBuffer?.log(tag, DEBUG, "animateCharge")
val startAnimPhase2 = Runnable {
setTextStyle(
weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -299,7 +314,7 @@ class AnimatableClockView @JvmOverloads constructor(
}
fun animateDoze(isDozing: Boolean, animate: Boolean) {
- lastAnimationCall = "${getTimestamp()} call=animateDoze"
+ logBuffer?.log(tag, DEBUG, "animateDoze")
setTextStyle(
weight = if (isDozing) dozingWeight else lockScreenWeight,
textSize = -1f,
@@ -311,7 +326,24 @@ class AnimatableClockView @JvmOverloads constructor(
)
}
- private val glyphFilter: GlyphCallback? = null // Add text animation tweak here.
+ // The offset of each glyph from where it should be.
+ private var glyphOffsets = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f)
+
+ private var lastSeenAnimationProgress = 1.0f
+
+ // If the animation is being reversed, the target offset for each glyph for the "stop".
+ private var animationCancelStartPosition = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f)
+ private var animationCancelStopPosition = 0.0f
+
+ // Whether the currently playing animation needed a stop (and thus, is shortened).
+ private var currentAnimationNeededStop = false
+
+ private val glyphFilter: GlyphCallback = { positionedGlyph, _ ->
+ val offset = positionedGlyph.lineNo * DIGITS_PER_LINE + positionedGlyph.glyphIndex
+ if (offset < glyphOffsets.size) {
+ positionedGlyph.x += glyphOffsets[offset]
+ }
+ }
/**
* Set text style with an optional animation.
@@ -345,6 +377,9 @@ class AnimatableClockView @JvmOverloads constructor(
onAnimationEnd = onAnimationEnd
)
textAnimator?.glyphFilter = glyphFilter
+ if (color != null && !isAnimationEnabled) {
+ setTextColor(color)
+ }
} else {
// when the text animator is set, update its start values
onTextAnimatorInitialized = Runnable {
@@ -359,6 +394,9 @@ class AnimatableClockView @JvmOverloads constructor(
onAnimationEnd = onAnimationEnd
)
textAnimator?.glyphFilter = glyphFilter
+ if (color != null && !isAnimationEnabled) {
+ setTextColor(color)
+ }
}
}
}
@@ -394,9 +432,12 @@ class AnimatableClockView @JvmOverloads constructor(
isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
else -> DOUBLE_LINE_FORMAT_12_HOUR
}
+ logBuffer?.log(tag, DEBUG,
+ { str1 = format?.toString() },
+ { "refreshFormat format=$str1" }
+ )
descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
-
refreshTime()
}
@@ -405,15 +446,8 @@ class AnimatableClockView @JvmOverloads constructor(
pw.println(" measuredWidth=$measuredWidth")
pw.println(" measuredHeight=$measuredHeight")
pw.println(" singleLineInternal=$isSingleLineInternal")
- pw.println(" lastTextUpdate=$lastTextUpdate")
- pw.println(" lastOnTextChanged=$lastOnTextChanged")
- pw.println(" lastInvalidate=$lastInvalidate")
- pw.println(" lastMeasureCall=$lastMeasureCall")
- pw.println(" lastDraw=$lastDraw")
- pw.println(" lastTimeZoneChange=$lastTimeZoneChange")
pw.println(" currText=$text")
pw.println(" currTimeContextDesc=$contentDescription")
- pw.println(" lastAnimationCall=$lastAnimationCall")
pw.println(" dozingWeightInternal=$dozingWeightInternal")
pw.println(" lockScreenWeightInternal=$lockScreenWeightInternal")
pw.println(" dozingColor=$dozingColor")
@@ -421,6 +455,124 @@ class AnimatableClockView @JvmOverloads constructor(
pw.println(" time=$time")
}
+ fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
+ // Do we need to cancel an in-flight animation?
+ // Need to also check against 0.0f here; we can sometimes get two calls with fraction == 0,
+ // which trips up the check otherwise.
+ if (lastSeenAnimationProgress != 1.0f &&
+ lastSeenAnimationProgress != 0.0f &&
+ fraction == 0.0f) {
+ // New animation, but need to stop the old one. Figure out where each glyph currently
+ // is in relation to the box position. After that, use the leading digit's current
+ // position as the stop target.
+ currentAnimationNeededStop = true
+
+ // We assume that the current glyph offsets would be relative to the "from" position.
+ val moveAmount = toRect.left - fromRect.left
+
+ // Remap the current glyph offsets to be relative to the new "end" position, and figure
+ // out the start/end positions for the stop animation.
+ for (i in 0 until NUM_DIGITS) {
+ glyphOffsets[i] = -moveAmount + glyphOffsets[i]
+ animationCancelStartPosition[i] = glyphOffsets[i]
+ }
+
+ // Use the leading digit's offset as the stop position.
+ if (toRect.left > fromRect.left) {
+ // It _was_ moving left
+ animationCancelStopPosition = glyphOffsets[0]
+ } else {
+ // It was moving right
+ animationCancelStopPosition = glyphOffsets[1]
+ }
+ }
+
+ // Is there a cancellation in progress?
+ if (currentAnimationNeededStop && fraction < ANIMATION_CANCELLATION_TIME) {
+ val animationStopProgress = MathUtils.constrainedMap(
+ 0.0f, 1.0f, 0.0f, ANIMATION_CANCELLATION_TIME, fraction
+ )
+
+ // One of the digits has already stopped.
+ val animationStopStep = 1.0f / (NUM_DIGITS - 1)
+
+ for (i in 0 until NUM_DIGITS) {
+ val stopAmount = if (toRect.left > fromRect.left) {
+ // It was moving left (before flipping)
+ MOVE_LEFT_DELAYS[i] * animationStopStep
+ } else {
+ // It was moving right (before flipping)
+ MOVE_RIGHT_DELAYS[i] * animationStopStep
+ }
+
+ // Leading digit stops immediately.
+ if (stopAmount == 0.0f) {
+ glyphOffsets[i] = animationCancelStopPosition
+ } else {
+ val actualStopAmount = MathUtils.constrainedMap(
+ 0.0f, 1.0f, 0.0f, stopAmount, animationStopProgress
+ )
+ val easedProgress = MOVE_INTERPOLATOR.getInterpolation(actualStopAmount)
+ val glyphMoveAmount =
+ animationCancelStopPosition - animationCancelStartPosition[i]
+ glyphOffsets[i] =
+ animationCancelStartPosition[i] + glyphMoveAmount * easedProgress
+ }
+ }
+ } else {
+ // Normal part of the animation.
+ // Do we need to remap the animation progress to take account of the cancellation?
+ val actualFraction = if (currentAnimationNeededStop) {
+ MathUtils.constrainedMap(
+ 0.0f, 1.0f, ANIMATION_CANCELLATION_TIME, 1.0f, fraction
+ )
+ } else {
+ fraction
+ }
+
+ val digitFractions = (0 until NUM_DIGITS).map {
+ // The delay for each digit, in terms of fraction (i.e. the digit should not move
+ // during 0.0 - 0.1).
+ val initialDelay = if (toRect.left > fromRect.left) {
+ MOVE_RIGHT_DELAYS[it] * MOVE_DIGIT_STEP
+ } else {
+ MOVE_LEFT_DELAYS[it] * MOVE_DIGIT_STEP
+ }
+
+ val f = MathUtils.constrainedMap(
+ 0.0f, 1.0f,
+ initialDelay, initialDelay + AVAILABLE_ANIMATION_TIME,
+ actualFraction
+ )
+ MOVE_INTERPOLATOR.getInterpolation(max(min(f, 1.0f), 0.0f))
+ }
+
+ // Was there an animation halt?
+ val moveAmount = if (currentAnimationNeededStop) {
+ // Only need to animate over the remaining space if the animation was aborted.
+ -animationCancelStopPosition
+ } else {
+ toRect.left.toFloat() - fromRect.left.toFloat()
+ }
+
+ for (i in 0 until NUM_DIGITS) {
+ glyphOffsets[i] = -moveAmount + (moveAmount * digitFractions[i])
+ }
+ }
+
+ invalidate()
+
+ if (fraction == 1.0f) {
+ // Reset
+ currentAnimationNeededStop = false
+ }
+
+ lastSeenAnimationProgress = fraction
+
+ // Ensure that the actual clock container is always in the "end" position.
+ this.setLeftTopRightBottom(toRect.left, toRect.top, toRect.right, toRect.bottom)
+ }
+
// DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
// This is an optimization to ensure we only recompute the patterns when the inputs change.
private object Patterns {
@@ -444,6 +596,7 @@ class AnimatableClockView @JvmOverloads constructor(
if (!clockView12Skel.contains("a")) {
sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' }
}
+
sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel)
sCacheKey = key
}
@@ -458,5 +611,36 @@ class AnimatableClockView @JvmOverloads constructor(
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
+
+ // Constants for the animation
+ private val MOVE_INTERPOLATOR = Interpolators.STANDARD
+
+ // Calculate the positions of all of the digits...
+ // Offset each digit by, say, 0.1
+ // This means that each digit needs to move over a slice of "fractions", i.e. digit 0 should
+ // move from 0.0 - 0.7, digit 1 from 0.1 - 0.8, digit 2 from 0.2 - 0.9, and digit 3
+ // from 0.3 - 1.0.
+ private const val NUM_DIGITS = 4
+ private const val DIGITS_PER_LINE = 2
+
+ // How much of "fraction" to spend on canceling the animation, if needed
+ private const val ANIMATION_CANCELLATION_TIME = 0.4f
+
+ // Delays. Each digit's animation should have a slight delay, so we get a nice
+ // "stepping" effect. When moving right, the second digit of the hour should move first.
+ // When moving left, the first digit of the hour should move first. The lists encode
+ // the delay for each digit (hour[0], hour[1], minute[0], minute[1]), to be multiplied
+ // by delayMultiplier.
+ private val MOVE_LEFT_DELAYS = listOf(0, 1, 2, 3)
+ private val MOVE_RIGHT_DELAYS = listOf(1, 0, 3, 2)
+
+ // How much delay to apply to each subsequent digit. This is measured in terms of "fraction"
+ // (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc
+ // before moving).
+ private const val MOVE_DIGIT_STEP = 0.1f
+
+ // Total available transition time for each digit, taking into account the step. If step is
+ // 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
+ private val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index f03fee4b0c2d..cd272635905b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -21,7 +21,7 @@ import android.os.Handler
import android.os.UserHandle
import android.provider.Settings
import android.util.Log
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.internal.annotations.Keep
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
@@ -30,7 +30,6 @@ import com.android.systemui.plugins.ClockProviderPlugin
import com.android.systemui.plugins.PluginListener
import com.android.systemui.shared.plugins.PluginManager
import com.google.gson.Gson
-import javax.inject.Inject
private val TAG = ClockRegistry::class.simpleName
private const val DEBUG = true
@@ -42,13 +41,6 @@ open class ClockRegistry(
val handler: Handler,
defaultClockProvider: ClockProvider
) {
- @Inject constructor(
- context: Context,
- pluginManager: PluginManager,
- @Main handler: Handler,
- defaultClockProvider: DefaultClockProvider
- ) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { }
-
// Usually this would be a typealias, but a SAM provides better java interop
fun interface ClockChangeListener {
fun onClockChanged()
@@ -201,6 +193,7 @@ open class ClockRegistry(
val provider: ClockProvider
)
+ @Keep
private data class ClockSetting(
val clockId: ClockId,
val _applied_timestamp: Long?
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index b88795157a43..da1d233949cf 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -16,6 +16,7 @@ package com.android.systemui.shared.clocks
import android.content.Context
import android.content.res.Resources
import android.graphics.Color
+import android.graphics.Rect
import android.icu.text.NumberFormat
import android.util.TypedValue
import android.view.LayoutInflater
@@ -26,6 +27,7 @@ import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
+import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.shared.R
import java.io.PrintWriter
import java.util.Locale
@@ -85,9 +87,17 @@ class DefaultClockController(
events.onTimeTick()
}
+ override fun setLogBuffer(logBuffer: LogBuffer) {
+ smallClock.view.tag = "smallClockView"
+ largeClock.view.tag = "largeClockView"
+ smallClock.view.logBuffer = logBuffer
+ largeClock.view.logBuffer = logBuffer
+ }
+
open inner class DefaultClockFaceController(
override val view: AnimatableClockView,
) : ClockFaceController {
+
// MAGENTA is a placeholder, and will be assigned correctly in initialize
private var currentColor = Color.MAGENTA
private var isRegionDark = false
@@ -130,6 +140,10 @@ class DefaultClockController(
lp.topMargin = (-0.5f * view.bottom).toInt()
view.setLayoutParams(lp)
}
+
+ fun moveForSplitShade(fromRect: Rect, toRect: Rect, fraction: Float) {
+ view.moveForSplitShade(fromRect, toRect, fraction)
+ }
}
inner class DefaultClockEvents : ClockEvents {
@@ -209,6 +223,13 @@ class DefaultClockController(
clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
}
}
+
+ override fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {
+ largeClock.moveForSplitShade(fromRect, toRect, fraction)
+ }
+
+ override val hasCustomPositionUpdatedAnimation: Boolean
+ get() = true
}
private class AnimationState(
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index 72f8b7b09dca..40c8774d4f34 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -1,13 +1,16 @@
package com.android.systemui.shared.recents.utilities;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.view.Surface;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.wm.shell.util.SplitBounds;
/**
* Utility class to position the thumbnail in the TaskView
@@ -16,10 +19,26 @@ public class PreviewPositionHelper {
public static final float MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT = 0.1f;
+ /**
+ * Specifies that a stage is positioned at the top half of the screen if
+ * in portrait mode or at the left half of the screen if in landscape mode.
+ * TODO(b/254378592): Remove after consolidation
+ */
+ public static final int STAGE_POSITION_TOP_OR_LEFT = 0;
+
+ /**
+ * Specifies that a stage is positioned at the bottom half of the screen if
+ * in portrait mode or at the right half of the screen if in landscape mode.
+ * TODO(b/254378592): Remove after consolidation
+ */
+ public static final int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+
// Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
private final RectF mClippedInsets = new RectF();
private final Matrix mMatrix = new Matrix();
private boolean mIsOrientationChanged;
+ private SplitBounds mSplitBounds;
+ private int mDesiredStagePosition;
public Matrix getMatrix() {
return mMatrix;
@@ -33,6 +52,11 @@ public class PreviewPositionHelper {
return mIsOrientationChanged;
}
+ public void setSplitBounds(SplitBounds splitBounds, int desiredStagePosition) {
+ mSplitBounds = splitBounds;
+ mDesiredStagePosition = desiredStagePosition;
+ }
+
/**
* Updates the matrix based on the provided parameters
*/
@@ -42,10 +66,19 @@ public class PreviewPositionHelper {
boolean isRotated = false;
boolean isOrientationDifferent;
+ float fullscreenTaskWidth = screenWidthPx;
+ if (mSplitBounds != null && !mSplitBounds.appsStackedVertically) {
+ // For landscape, scale the width
+ float taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
+ ? mSplitBounds.leftTaskPercent
+ : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
+ // Scale landscape width to that of actual screen
+ fullscreenTaskWidth = screenWidthPx * taskPercent;
+ }
int thumbnailRotation = thumbnailData.rotation;
int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
RectF thumbnailClipHint = new RectF();
- float canvasScreenRatio = canvasWidth / (float) screenWidthPx;
+ float canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
float scaledTaskbarSize = taskbarSize * canvasScreenRatio;
thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
@@ -180,7 +213,7 @@ public class PreviewPositionHelper {
* portrait or vice versa, {@code false} otherwise
*/
private boolean isOrientationChange(int deltaRotation) {
- return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
+ return deltaRotation == ROTATION_90 || deltaRotation == ROTATION_270;
}
private void setThumbnailRotation(int deltaRotate, Rect thumbnailPosition) {
@@ -189,13 +222,13 @@ public class PreviewPositionHelper {
mMatrix.setRotate(90 * deltaRotate);
switch (deltaRotate) { /* Counter-clockwise */
- case Surface.ROTATION_90:
+ case ROTATION_90:
translateX = thumbnailPosition.height();
break;
- case Surface.ROTATION_270:
+ case ROTATION_270:
translateY = thumbnailPosition.width();
break;
- case Surface.ROTATION_180:
+ case ROTATION_180:
translateX = thumbnailPosition.width();
translateY = thumbnailPosition.height();
break;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
index dd2e55d4e7d7..cd4b9994ccca 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.shared.regionsampling
+import android.graphics.Color
import android.graphics.Rect
import android.view.View
import androidx.annotation.VisibleForTesting
@@ -33,18 +34,19 @@ open class RegionSamplingInstance(
regionSamplingEnabled: Boolean,
updateFun: UpdateColorCallback
) {
- private var isDark = RegionDarkness.DEFAULT
+ private var regionDarkness = RegionDarkness.DEFAULT
private var samplingBounds = Rect()
private val tmpScreenLocation = IntArray(2)
@VisibleForTesting var regionSampler: RegionSamplingHelper? = null
-
+ private var lightForegroundColor = Color.WHITE
+ private var darkForegroundColor = Color.BLACK
/**
* Interface for method to be passed into RegionSamplingHelper
*/
@FunctionalInterface
interface UpdateColorCallback {
/**
- * Method to update the text colors after clock darkness changed.
+ * Method to update the foreground colors after clock darkness changed.
*/
fun updateColors()
}
@@ -59,6 +61,30 @@ open class RegionSamplingInstance(
return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor)
}
+ /**
+ * Sets the colors to be used for Dark and Light Foreground.
+ *
+ * @param lightColor The color used for Light Foreground.
+ * @param darkColor The color used for Dark Foreground.
+ */
+ fun setForegroundColors(lightColor: Int, darkColor: Int) {
+ lightForegroundColor = lightColor
+ darkForegroundColor = darkColor
+ }
+
+ /**
+ * Determines which foreground color to use based on region darkness.
+ *
+ * @return the determined foreground color
+ */
+ fun currentForegroundColor(): Int{
+ return if (regionDarkness.isDark) {
+ lightForegroundColor
+ } else {
+ darkForegroundColor
+ }
+ }
+
private fun convertToClockDarkness(isRegionDark: Boolean): RegionDarkness {
return if (isRegionDark) {
RegionDarkness.DARK
@@ -68,7 +94,7 @@ open class RegionSamplingInstance(
}
fun currentRegionDarkness(): RegionDarkness {
- return isDark
+ return regionDarkness
}
/**
@@ -97,7 +123,7 @@ open class RegionSamplingInstance(
regionSampler = createRegionSamplingHelper(sampledView,
object : SamplingCallback {
override fun onRegionDarknessChanged(isRegionDark: Boolean) {
- isDark = convertToClockDarkness(isRegionDark)
+ regionDarkness = convertToClockDarkness(isRegionDark)
updateFun.updateColors()
}
/**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index ce337bb70e5b..fd41cb0630dd 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
@@ -194,12 +194,8 @@ public class ActivityManagerWrapper {
Rect homeContentInsets, Rect minimizedHomeBounds) {
final RecentsAnimationControllerCompat controllerCompat =
new RecentsAnimationControllerCompat(controller);
- final RemoteAnimationTargetCompat[] appsCompat =
- RemoteAnimationTargetCompat.wrap(apps);
- final RemoteAnimationTargetCompat[] wallpapersCompat =
- RemoteAnimationTargetCompat.wrap(wallpapers);
- animationHandler.onAnimationStart(controllerCompat, appsCompat,
- wallpapersCompat, homeContentInsets, minimizedHomeBounds);
+ animationHandler.onAnimationStart(controllerCompat, apps,
+ wallpapers, homeContentInsets, minimizedHomeBounds);
}
@Override
@@ -210,12 +206,7 @@ public class ActivityManagerWrapper {
@Override
public void onTasksAppeared(RemoteAnimationTarget[] apps) {
- final RemoteAnimationTargetCompat[] compats =
- new RemoteAnimationTargetCompat[apps.length];
- for (int i = 0; i < apps.length; ++i) {
- compats[i] = new RemoteAnimationTargetCompat(apps[i]);
- }
- animationHandler.onTasksAppeared(compats);
+ animationHandler.onTasksAppeared(apps);
}
};
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index 5d6598d63a1b..8a2509610310 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -51,6 +51,8 @@ public final class InteractionJankMonitorWrapper {
InteractionJankMonitor.CUJ_SPLIT_SCREEN_ENTER;
public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION =
InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
+ public static final int CUJ_RECENTS_SCROLLING =
+ InteractionJankMonitor.CUJ_RECENTS_SCROLLING;
@IntDef({
CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -59,7 +61,8 @@ public final class InteractionJankMonitorWrapper {
CUJ_APP_CLOSE_TO_PIP,
CUJ_QUICK_SWITCH,
CUJ_APP_LAUNCH_FROM_WIDGET,
- CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION
+ CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION,
+ CUJ_RECENTS_SCROLLING
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index f2742b7889b1..766266d9cc94 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
@@ -110,6 +110,9 @@ public class QuickStepContract {
public static final int SYSUI_STATE_IMMERSIVE_MODE = 1 << 24;
// The voice interaction session window is showing
public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25;
+ // Freeform windows are showing in desktop mode
+ public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -137,7 +140,8 @@ public class QuickStepContract {
SYSUI_STATE_BACK_DISABLED,
SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
SYSUI_STATE_IMMERSIVE_MODE,
- SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING
+ SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING,
+ SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE
})
public @interface SystemUiStateFlags {}
@@ -173,6 +177,8 @@ public class QuickStepContract {
? "bubbles_mange_menu_expanded" : "");
str.add((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0 ? "immersive_mode" : "");
str.add((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0 ? "vis_win_showing" : "");
+ str.add((flags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0
+ ? "freeform_active_in_desktop_mode" : "");
return str.toString();
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index 5cca4a6a65f0..8bddf217ccb4 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -17,6 +17,7 @@
package com.android.systemui.shared.system;
import android.graphics.Rect;
+import android.view.RemoteAnimationTarget;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -27,7 +28,7 @@ public interface RecentsAnimationListener {
* Called when the animation into Recents can start. This call is made on the binder thread.
*/
void onAnimationStart(RecentsAnimationControllerCompat controller,
- RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers,
+ RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
Rect homeContentInsets, Rect minimizedHomeBounds);
/**
@@ -39,7 +40,7 @@ public interface RecentsAnimationListener {
* Called when the task of an activity that has been started while the recents animation
* was running becomes ready for control.
*/
- void onTasksAppeared(RemoteAnimationTargetCompat[] app);
+ void onTasksAppeared(RemoteAnimationTarget[] app);
/**
* Called to request that the current task tile be switched out for a screenshot (if not
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 09cf7c57c08a..37e706a9a4c9 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
@@ -83,12 +83,6 @@ public class RemoteAnimationAdapterCompat {
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
final IRemoteAnimationFinishedCallback finishedCallback) {
- final RemoteAnimationTargetCompat[] appsCompat =
- RemoteAnimationTargetCompat.wrap(apps);
- final RemoteAnimationTargetCompat[] wallpapersCompat =
- RemoteAnimationTargetCompat.wrap(wallpapers);
- final RemoteAnimationTargetCompat[] nonAppsCompat =
- RemoteAnimationTargetCompat.wrap(nonApps);
final Runnable animationFinishedCallback = new Runnable() {
@Override
public void run() {
@@ -100,8 +94,8 @@ public class RemoteAnimationAdapterCompat {
}
}
};
- remoteAnimationAdapter.onAnimationStart(transit, appsCompat, wallpapersCompat,
- nonAppsCompat, animationFinishedCallback);
+ remoteAnimationAdapter.onAnimationStart(transit, apps, wallpapers,
+ nonApps, animationFinishedCallback);
}
@Override
@@ -121,12 +115,12 @@ public class RemoteAnimationAdapterCompat {
SurfaceControl.Transaction t,
IRemoteTransitionFinishedCallback finishCallback) {
final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
- final RemoteAnimationTargetCompat[] appsCompat =
+ final RemoteAnimationTarget[] apps =
RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
- final RemoteAnimationTargetCompat[] wallpapersCompat =
+ final RemoteAnimationTarget[] wallpapers =
RemoteAnimationTargetCompat.wrapNonApps(
info, true /* wallpapers */, t, leashMap);
- final RemoteAnimationTargetCompat[] nonAppsCompat =
+ final RemoteAnimationTarget[] nonApps =
RemoteAnimationTargetCompat.wrapNonApps(
info, false /* wallpapers */, t, leashMap);
@@ -189,9 +183,9 @@ public class RemoteAnimationAdapterCompat {
}
}
// Make wallpaper visible immediately since launcher apparently won't do this.
- for (int i = wallpapersCompat.length - 1; i >= 0; --i) {
- t.show(wallpapersCompat[i].leash);
- t.setAlpha(wallpapersCompat[i].leash, 1.f);
+ for (int i = wallpapers.length - 1; i >= 0; --i) {
+ t.show(wallpapers[i].leash);
+ t.setAlpha(wallpapers[i].leash, 1.f);
}
} else {
if (launcherTask != null) {
@@ -237,7 +231,7 @@ public class RemoteAnimationAdapterCompat {
}
// TODO(bc-unlcok): Pass correct transit type.
remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE,
- appsCompat, wallpapersCompat, nonAppsCompat, () -> {
+ apps, wallpapers, nonApps, () -> {
synchronized (mFinishRunnables) {
if (mFinishRunnables.remove(token) == null) return;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 007629254c7c..5809c8124946 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -16,11 +16,12 @@
package com.android.systemui.shared.system;
+import android.view.RemoteAnimationTarget;
import android.view.WindowManager;
public interface RemoteAnimationRunnerCompat {
void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers,
- RemoteAnimationTargetCompat[] nonApps, Runnable finishedCallback);
+ RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps, Runnable finishedCallback);
void onAnimationCancelled();
} \ No newline at end of file
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 7c3b5fc52f0a..8d1768c41589 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
@@ -11,12 +11,15 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.shared.system;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.view.RemoteAnimationTarget.MODE_CHANGING;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -29,88 +32,28 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
-import android.graphics.Point;
import android.graphics.Rect;
import android.util.ArrayMap;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
+import android.window.TransitionInfo.Change;
import java.util.ArrayList;
+import java.util.function.BiPredicate;
/**
- * @see RemoteAnimationTarget
+ * Some utility methods for creating {@link RemoteAnimationTarget} instances.
*/
public class RemoteAnimationTargetCompat {
- public static final int MODE_OPENING = RemoteAnimationTarget.MODE_OPENING;
- public static final int MODE_CLOSING = RemoteAnimationTarget.MODE_CLOSING;
- public static final int MODE_CHANGING = RemoteAnimationTarget.MODE_CHANGING;
- public final int mode;
-
- public static final int ACTIVITY_TYPE_UNDEFINED = WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
- public static final int ACTIVITY_TYPE_STANDARD = WindowConfiguration.ACTIVITY_TYPE_STANDARD;
- public static final int ACTIVITY_TYPE_HOME = WindowConfiguration.ACTIVITY_TYPE_HOME;
- public static final int ACTIVITY_TYPE_RECENTS = WindowConfiguration.ACTIVITY_TYPE_RECENTS;
- public static final int ACTIVITY_TYPE_ASSISTANT = WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
- public final int activityType;
-
- public int taskId;
- public final SurfaceControl leash;
- public final boolean isTranslucent;
- public final Rect clipRect;
- public final int prefixOrderIndex;
- public final Point position;
- public final Rect localBounds;
- public final Rect sourceContainerBounds;
- public final Rect screenSpaceBounds;
- public final Rect startScreenSpaceBounds;
- public final boolean isNotInRecents;
- public final Rect contentInsets;
- public ActivityManager.RunningTaskInfo taskInfo;
- public final boolean allowEnterPip;
- public final int rotationChange;
- public final int windowType;
- public final WindowConfiguration windowConfiguration;
-
- private final SurfaceControl mStartLeash;
-
- // Fields used only to unwrap into RemoteAnimationTarget
- private final Rect startBounds;
-
- public final boolean willShowImeOnTarget;
-
- public RemoteAnimationTargetCompat(RemoteAnimationTarget app) {
- taskId = app.taskId;
- mode = app.mode;
- leash = app.leash;
- isTranslucent = app.isTranslucent;
- clipRect = app.clipRect;
- position = app.position;
- localBounds = app.localBounds;
- sourceContainerBounds = app.sourceContainerBounds;
- screenSpaceBounds = app.screenSpaceBounds;
- startScreenSpaceBounds = screenSpaceBounds;
- prefixOrderIndex = app.prefixOrderIndex;
- isNotInRecents = app.isNotInRecents;
- contentInsets = app.contentInsets;
- activityType = app.windowConfiguration.getActivityType();
- taskInfo = app.taskInfo;
- allowEnterPip = app.allowEnterPip;
- rotationChange = 0;
-
- mStartLeash = app.startLeash;
- windowType = app.windowType;
- windowConfiguration = app.windowConfiguration;
- startBounds = app.startBounds;
- willShowImeOnTarget = app.willShowImeOnTarget;
- }
-
private static int newModeToLegacyMode(int newMode) {
switch (newMode) {
case WindowManager.TRANSIT_OPEN:
@@ -120,20 +63,10 @@ public class RemoteAnimationTargetCompat {
case WindowManager.TRANSIT_TO_BACK:
return MODE_CLOSING;
default:
- return 2; // MODE_CHANGING
+ return MODE_CHANGING;
}
}
- public RemoteAnimationTarget unwrap() {
- final RemoteAnimationTarget target = new RemoteAnimationTarget(
- taskId, mode, leash, isTranslucent, clipRect, contentInsets,
- prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfiguration,
- isNotInRecents, mStartLeash, startBounds, taskInfo, allowEnterPip, windowType
- );
- target.setWillShowImeOnTarget(willShowImeOnTarget);
- return target;
- }
-
/**
* Almost a copy of Transitions#setupStartState.
* TODO: remove when there is proper cross-process transaction sync.
@@ -205,54 +138,61 @@ public class RemoteAnimationTargetCompat {
return leashSurface;
}
- public RemoteAnimationTargetCompat(TransitionInfo.Change change, int order,
- TransitionInfo info, SurfaceControl.Transaction t) {
- mode = newModeToLegacyMode(change.getMode());
+ /**
+ * Creates a new RemoteAnimationTarget from the provided change info
+ */
+ public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
+ TransitionInfo info, SurfaceControl.Transaction t,
+ @Nullable ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
+ int taskId;
+ boolean isNotInRecents;
+ ActivityManager.RunningTaskInfo taskInfo;
+ WindowConfiguration windowConfiguration;
+
taskInfo = change.getTaskInfo();
if (taskInfo != null) {
taskId = taskInfo.taskId;
isNotInRecents = !taskInfo.isRunning;
- activityType = taskInfo.getActivityType();
windowConfiguration = taskInfo.configuration.windowConfiguration;
} else {
taskId = INVALID_TASK_ID;
isNotInRecents = true;
- activityType = ACTIVITY_TYPE_UNDEFINED;
windowConfiguration = new WindowConfiguration();
}
- // TODO: once we can properly sync transactions across process, then get rid of this leash.
- leash = createLeash(info, change, order, t);
-
- isTranslucent = (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0;
- clipRect = null;
- position = null;
- localBounds = new Rect(change.getEndAbsBounds());
+ Rect localBounds = new Rect(change.getEndAbsBounds());
localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
- sourceContainerBounds = null;
- screenSpaceBounds = new Rect(change.getEndAbsBounds());
- startScreenSpaceBounds = new Rect(change.getStartAbsBounds());
-
- prefixOrderIndex = order;
- // TODO(shell-transitions): I guess we need to send content insets? evaluate how its used.
- contentInsets = new Rect(0, 0, 0, 0);
- allowEnterPip = change.getAllowEnterPip();
- mStartLeash = null;
- rotationChange = change.getEndRotation() - change.getStartRotation();
- windowType = (change.getFlags() & FLAG_IS_DIVIDER_BAR) != 0
- ? TYPE_DOCK_DIVIDER : INVALID_WINDOW_TYPE;
-
- startBounds = change.getStartAbsBounds();
- willShowImeOnTarget = (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0;
- }
- public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
- final int length = apps != null ? apps.length : 0;
- final RemoteAnimationTargetCompat[] appsCompat = new RemoteAnimationTargetCompat[length];
- for (int i = 0; i < length; i++) {
- appsCompat[i] = new RemoteAnimationTargetCompat(apps[i]);
+ RemoteAnimationTarget target = new RemoteAnimationTarget(
+ taskId,
+ newModeToLegacyMode(change.getMode()),
+ // TODO: once we can properly sync transactions across process,
+ // then get rid of this leash.
+ createLeash(info, change, order, t),
+ (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0,
+ null,
+ // TODO(shell-transitions): we need to send content insets? evaluate how its used.
+ new Rect(0, 0, 0, 0),
+ order,
+ null,
+ localBounds,
+ new Rect(change.getEndAbsBounds()),
+ windowConfiguration,
+ isNotInRecents,
+ null,
+ new Rect(change.getStartAbsBounds()),
+ taskInfo,
+ change.getAllowEnterPip(),
+ (change.getFlags() & FLAG_IS_DIVIDER_BAR) != 0
+ ? TYPE_DOCK_DIVIDER : INVALID_WINDOW_TYPE
+ );
+ target.setWillShowImeOnTarget(
+ (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0);
+ target.setRotationChange(change.getEndRotation() - change.getStartRotation());
+ if (leashMap != null) {
+ leashMap.put(change.getLeash(), target.leash);
}
- return appsCompat;
+ return target;
}
/**
@@ -261,35 +201,20 @@ public class RemoteAnimationTargetCompat {
* @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be
* populated by this function. If null, it is ignored.
*/
- public static RemoteAnimationTargetCompat[] wrapApps(TransitionInfo info,
+ public static RemoteAnimationTarget[] wrapApps(TransitionInfo info,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
- final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>();
- final SparseArray<TransitionInfo.Change> childTaskTargets = new SparseArray<>();
- for (int i = 0; i < info.getChanges().size(); i++) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() == null) continue;
-
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ SparseBooleanArray childTaskTargets = new SparseBooleanArray();
+ return wrap(info, t, leashMap, (change, taskInfo) -> {
// 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;
- }
- if (taskInfo.hasParentTask()) {
- childTaskTargets.put(taskInfo.parentTaskId, change);
- }
+ if ((taskInfo == null) || childTaskTargets.get(taskInfo.taskId)) {
+ // has children, so not a leaf. Skip.
+ return false;
}
-
- final RemoteAnimationTargetCompat targetCompat =
- new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t);
- if (leashMap != null) {
- leashMap.put(change.getLeash(), targetCompat.leash);
+ if (taskInfo.hasParentTask()) {
+ childTaskTargets.put(taskInfo.parentTaskId, true);
}
- out.add(targetCompat);
- }
-
- return out.toArray(new RemoteAnimationTargetCompat[out.size()]);
+ return true;
+ });
}
/**
@@ -300,38 +225,22 @@ public class RemoteAnimationTargetCompat {
* @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be
* populated by this function. If null, it is ignored.
*/
- public static RemoteAnimationTargetCompat[] wrapNonApps(TransitionInfo info, boolean wallpapers,
+ public static RemoteAnimationTarget[] wrapNonApps(TransitionInfo info, boolean wallpapers,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
- final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>();
+ return wrap(info, t, leashMap, (change, taskInfo) -> (taskInfo == null)
+ && wallpapers == ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0));
+ }
+ private static RemoteAnimationTarget[] wrap(TransitionInfo info,
+ SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
+ BiPredicate<Change, TaskInfo> filter) {
+ final ArrayList<RemoteAnimationTarget> out = new ArrayList<>();
for (int i = 0; i < info.getChanges().size(); i++) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() != null) continue;
-
- 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);
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (filter.test(change, change.getTaskInfo())) {
+ out.add(newTarget(change, info.getChanges().size() - i, info, t, leashMap));
}
- out.add(targetCompat);
- }
-
- return out.toArray(new RemoteAnimationTargetCompat[out.size()]);
- }
-
- /**
- * @see SurfaceControl#release()
- */
- public void release() {
- if (leash != null) {
- leash.release();
- }
- if (mStartLeash != null) {
- mStartLeash.release();
}
+ return out.toArray(new RemoteAnimationTarget[out.size()]);
}
}
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 f6792251d282..d6655a74219c 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
@@ -17,7 +17,9 @@
package com.android.systemui.shared.system;
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.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
@@ -27,8 +29,7 @@ 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 static com.android.systemui.shared.system.RemoteAnimationTargetCompat.newTarget;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -45,6 +46,7 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
import android.view.IRecentsAnimationController;
+import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
@@ -127,9 +129,9 @@ public class RemoteTransitionCompat implements Parcelable {
SurfaceControl.Transaction t,
IRemoteTransitionFinishedCallback finishedCallback) {
final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
- final RemoteAnimationTargetCompat[] apps =
+ final RemoteAnimationTarget[] apps =
RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
- final RemoteAnimationTargetCompat[] wallpapers =
+ final RemoteAnimationTarget[] wallpapers =
RemoteAnimationTargetCompat.wrapNonApps(
info, true /* wallpapers */, t, leashMap);
// TODO(b/177438007): Move this set-up logic into launcher's animation impl.
@@ -230,7 +232,7 @@ public class RemoteTransitionCompat implements Parcelable {
private PictureInPictureSurfaceTransaction mPipTransaction = null;
private IBinder mTransition = null;
private boolean mKeyguardLocked = false;
- private RemoteAnimationTargetCompat[] mAppearedTargets;
+ private RemoteAnimationTarget[] mAppearedTargets;
private boolean mWillFinishToHome = false;
void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
@@ -325,18 +327,15 @@ public class RemoteTransitionCompat implements Parcelable {
final int layer = mInfo.getChanges().size() * 3;
mOpeningLeashes = new ArrayList<>();
mOpeningHome = cancelRecents;
- final RemoteAnimationTargetCompat[] targets =
- new RemoteAnimationTargetCompat[openingTasks.size()];
+ final RemoteAnimationTarget[] targets =
+ new RemoteAnimationTarget[openingTasks.size()];
for (int i = 0; i < openingTasks.size(); ++i) {
final TransitionInfo.Change change = openingTasks.valueAt(i);
mOpeningLeashes.add(change.getLeash());
// We are receiving new opening tasks, so convert to onTasksAppeared.
- final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat(
- change, layer, info, t);
- mLeashMap.put(mOpeningLeashes.get(i), target.leash);
- t.reparent(target.leash, mInfo.getRootLeash());
- t.setLayer(target.leash, layer);
- targets[i] = target;
+ targets[i] = newTarget(change, layer, info, t, mLeashMap);
+ t.reparent(targets[i].leash, mInfo.getRootLeash());
+ t.setLayer(targets[i].leash, layer);
}
t.apply();
mAppearedTargets = targets;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
deleted file mode 100644
index 30c062b66da9..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import android.graphics.HardwareRenderer;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Handler.Callback;
-import android.os.Message;
-import android.os.Trace;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.View;
-import android.view.ViewRootImpl;
-
-import java.util.function.Consumer;
-
-/**
- * Helper class to apply surface transactions in sync with RenderThread.
- *
- * NOTE: This is a modification of {@link android.view.SyncRtSurfaceTransactionApplier}, we can't
- * currently reference that class from the shared lib as it is hidden.
- */
-public class SyncRtSurfaceTransactionApplierCompat {
-
- public static final int FLAG_ALL = 0xffffffff;
- public static final int FLAG_ALPHA = 1;
- public static final int FLAG_MATRIX = 1 << 1;
- public static final int FLAG_WINDOW_CROP = 1 << 2;
- public static final int FLAG_LAYER = 1 << 3;
- public static final int FLAG_CORNER_RADIUS = 1 << 4;
- public static final int FLAG_BACKGROUND_BLUR_RADIUS = 1 << 5;
- public static final int FLAG_VISIBILITY = 1 << 6;
- public static final int FLAG_RELATIVE_LAYER = 1 << 7;
- public static final int FLAG_SHADOW_RADIUS = 1 << 8;
-
- private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0;
-
- private final SurfaceControl mBarrierSurfaceControl;
- private final ViewRootImpl mTargetViewRootImpl;
- private final Handler mApplyHandler;
-
- private int mSequenceNumber = 0;
- private int mPendingSequenceNumber = 0;
- private Runnable mAfterApplyCallback;
-
- /**
- * @param targetView The view in the surface that acts as synchronization anchor.
- */
- public SyncRtSurfaceTransactionApplierCompat(View targetView) {
- mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
- mBarrierSurfaceControl = mTargetViewRootImpl != null
- ? mTargetViewRootImpl.getSurfaceControl() : null;
-
- mApplyHandler = new Handler(new Callback() {
- @Override
- public boolean handleMessage(Message msg) {
- if (msg.what == MSG_UPDATE_SEQUENCE_NUMBER) {
- onApplyMessage(msg.arg1);
- return true;
- }
- return false;
- }
- });
- }
-
- private void onApplyMessage(int seqNo) {
- mSequenceNumber = seqNo;
- if (mSequenceNumber == mPendingSequenceNumber && mAfterApplyCallback != null) {
- Runnable r = mAfterApplyCallback;
- mAfterApplyCallback = null;
- r.run();
- }
- }
-
- /**
- * Schedules applying surface parameters on the next frame.
- *
- * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into
- * this method to avoid synchronization issues.
- */
- public void scheduleApply(final SyncRtSurfaceTransactionApplierCompat.SurfaceParams... params) {
- if (mTargetViewRootImpl == null || mTargetViewRootImpl.getView() == null) {
- return;
- }
-
- mPendingSequenceNumber++;
- final int toApplySeqNo = mPendingSequenceNumber;
- mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() {
- @Override
- public void onFrameDraw(long frame) {
- if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) {
- Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
- .sendToTarget();
- return;
- }
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Sync transaction frameNumber=" + frame);
- Transaction t = new Transaction();
- for (int i = params.length - 1; i >= 0; i--) {
- SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams =
- params[i];
- surfaceParams.applyTo(t);
- }
- if (mTargetViewRootImpl != null) {
- mTargetViewRootImpl.mergeWithNextTransaction(t, frame);
- } else {
- t.apply();
- }
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
- .sendToTarget();
- }
- });
-
- // Make sure a frame gets scheduled.
- mTargetViewRootImpl.getView().invalidate();
- }
-
- /**
- * Calls the runnable when any pending apply calls have completed
- */
- public void addAfterApplyCallback(final Runnable afterApplyCallback) {
- if (mSequenceNumber == mPendingSequenceNumber) {
- afterApplyCallback.run();
- } else {
- if (mAfterApplyCallback == null) {
- mAfterApplyCallback = afterApplyCallback;
- } else {
- final Runnable oldCallback = mAfterApplyCallback;
- mAfterApplyCallback = new Runnable() {
- @Override
- public void run() {
- afterApplyCallback.run();
- oldCallback.run();
- }
- };
- }
- }
- }
-
- public static void applyParams(TransactionCompat t,
- SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) {
- params.applyTo(t.mTransaction);
- }
-
- /**
- * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is
- * attached if necessary.
- */
- public static void create(final View targetView,
- final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) {
- if (targetView == null) {
- // No target view, no applier
- callback.accept(null);
- } else if (targetView.getViewRootImpl() != null) {
- // Already attached, we're good to go
- callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
- } else {
- // Haven't been attached before we can get the view root
- targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- targetView.removeOnAttachStateChangeListener(this);
- callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView));
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- // Do nothing
- }
- });
- }
- }
-
- public static class SurfaceParams {
- public static class Builder {
- final SurfaceControl surface;
- int flags;
- float alpha;
- float cornerRadius;
- int backgroundBlurRadius;
- Matrix matrix;
- Rect windowCrop;
- int layer;
- SurfaceControl relativeTo;
- int relativeLayer;
- boolean visible;
- float shadowRadius;
-
- /**
- * @param surface The surface to modify.
- */
- public Builder(SurfaceControl surface) {
- this.surface = surface;
- }
-
- /**
- * @param alpha The alpha value to apply to the surface.
- * @return this Builder
- */
- public Builder withAlpha(float alpha) {
- this.alpha = alpha;
- flags |= FLAG_ALPHA;
- return this;
- }
-
- /**
- * @param matrix The matrix to apply to the surface.
- * @return this Builder
- */
- public Builder withMatrix(Matrix matrix) {
- this.matrix = new Matrix(matrix);
- flags |= FLAG_MATRIX;
- return this;
- }
-
- /**
- * @param windowCrop The window crop to apply to the surface.
- * @return this Builder
- */
- public Builder withWindowCrop(Rect windowCrop) {
- this.windowCrop = new Rect(windowCrop);
- flags |= FLAG_WINDOW_CROP;
- return this;
- }
-
- /**
- * @param layer The layer to assign the surface.
- * @return this Builder
- */
- public Builder withLayer(int layer) {
- this.layer = layer;
- flags |= FLAG_LAYER;
- return this;
- }
-
- /**
- * @param relativeTo The surface that's set relative layer to.
- * @param relativeLayer The relative layer.
- * @return this Builder
- */
- public Builder withRelativeLayerTo(SurfaceControl relativeTo, int relativeLayer) {
- this.relativeTo = relativeTo;
- this.relativeLayer = relativeLayer;
- flags |= FLAG_RELATIVE_LAYER;
- return this;
- }
-
- /**
- * @param radius the Radius for rounded corners to apply to the surface.
- * @return this Builder
- */
- public Builder withCornerRadius(float radius) {
- this.cornerRadius = radius;
- flags |= FLAG_CORNER_RADIUS;
- return this;
- }
-
- /**
- * @param radius the Radius for the shadows to apply to the surface.
- * @return this Builder
- */
- public Builder withShadowRadius(float radius) {
- this.shadowRadius = radius;
- flags |= FLAG_SHADOW_RADIUS;
- return this;
- }
-
- /**
- * @param radius the Radius for blur to apply to the background surfaces.
- * @return this Builder
- */
- public Builder withBackgroundBlur(int radius) {
- this.backgroundBlurRadius = radius;
- flags |= FLAG_BACKGROUND_BLUR_RADIUS;
- return this;
- }
-
- /**
- * @param visible The visibility to apply to the surface.
- * @return this Builder
- */
- public Builder withVisibility(boolean visible) {
- this.visible = visible;
- flags |= FLAG_VISIBILITY;
- return this;
- }
-
- /**
- * @return a new SurfaceParams instance
- */
- public SurfaceParams build() {
- return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer,
- relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible,
- shadowRadius);
- }
- }
-
- private SurfaceParams(SurfaceControl surface, int flags, float alpha, Matrix matrix,
- Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer,
- float cornerRadius, int backgroundBlurRadius, boolean visible, float shadowRadius) {
- this.flags = flags;
- this.surface = surface;
- this.alpha = alpha;
- this.matrix = matrix;
- this.windowCrop = windowCrop;
- this.layer = layer;
- this.relativeTo = relativeTo;
- this.relativeLayer = relativeLayer;
- this.cornerRadius = cornerRadius;
- this.backgroundBlurRadius = backgroundBlurRadius;
- this.visible = visible;
- this.shadowRadius = shadowRadius;
- }
-
- private final int flags;
- private final float[] mTmpValues = new float[9];
-
- public final SurfaceControl surface;
- public final float alpha;
- public final float cornerRadius;
- public final int backgroundBlurRadius;
- public final Matrix matrix;
- public final Rect windowCrop;
- public final int layer;
- public final SurfaceControl relativeTo;
- public final int relativeLayer;
- public final boolean visible;
- public final float shadowRadius;
-
- public void applyTo(SurfaceControl.Transaction t) {
- if ((flags & FLAG_MATRIX) != 0) {
- t.setMatrix(surface, matrix, mTmpValues);
- }
- if ((flags & FLAG_WINDOW_CROP) != 0) {
- t.setWindowCrop(surface, windowCrop);
- }
- if ((flags & FLAG_ALPHA) != 0) {
- t.setAlpha(surface, alpha);
- }
- if ((flags & FLAG_LAYER) != 0) {
- t.setLayer(surface, layer);
- }
- if ((flags & FLAG_CORNER_RADIUS) != 0) {
- t.setCornerRadius(surface, cornerRadius);
- }
- if ((flags & FLAG_BACKGROUND_BLUR_RADIUS) != 0) {
- t.setBackgroundBlurRadius(surface, backgroundBlurRadius);
- }
- if ((flags & FLAG_VISIBILITY) != 0) {
- if (visible) {
- t.show(surface);
- } else {
- t.hide(surface);
- }
- }
- if ((flags & FLAG_RELATIVE_LAYER) != 0) {
- t.setRelativeLayer(surface, relativeTo, relativeLayer);
- }
- if ((flags & FLAG_SHADOW_RADIUS) != 0) {
- t.setShadowRadius(surface, shadowRadius);
- }
- }
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
deleted file mode 100644
index 43a882a5f6be..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-
-public class TransactionCompat {
-
- final Transaction mTransaction;
-
- final float[] mTmpValues = new float[9];
-
- public TransactionCompat() {
- mTransaction = new Transaction();
- }
-
- public void apply() {
- mTransaction.apply();
- }
-
- public TransactionCompat show(SurfaceControl surfaceControl) {
- mTransaction.show(surfaceControl);
- return this;
- }
-
- public TransactionCompat hide(SurfaceControl surfaceControl) {
- mTransaction.hide(surfaceControl);
- return this;
- }
-
- public TransactionCompat setPosition(SurfaceControl surfaceControl, float x, float y) {
- mTransaction.setPosition(surfaceControl, x, y);
- return this;
- }
-
- public TransactionCompat setSize(SurfaceControl surfaceControl, int w, int h) {
- mTransaction.setBufferSize(surfaceControl, w, h);
- return this;
- }
-
- public TransactionCompat setLayer(SurfaceControl surfaceControl, int z) {
- mTransaction.setLayer(surfaceControl, z);
- return this;
- }
-
- public TransactionCompat setAlpha(SurfaceControl surfaceControl, float alpha) {
- mTransaction.setAlpha(surfaceControl, alpha);
- return this;
- }
-
- public TransactionCompat setOpaque(SurfaceControl surfaceControl, boolean opaque) {
- mTransaction.setOpaque(surfaceControl, opaque);
- return this;
- }
-
- public TransactionCompat setMatrix(SurfaceControl surfaceControl, float dsdx, float dtdx,
- float dtdy, float dsdy) {
- mTransaction.setMatrix(surfaceControl, dsdx, dtdx, dtdy, dsdy);
- return this;
- }
-
- public TransactionCompat setMatrix(SurfaceControl surfaceControl, Matrix matrix) {
- mTransaction.setMatrix(surfaceControl, matrix, mTmpValues);
- return this;
- }
-
- public TransactionCompat setWindowCrop(SurfaceControl surfaceControl, Rect crop) {
- mTransaction.setWindowCrop(surfaceControl, crop);
- return this;
- }
-
- public TransactionCompat setCornerRadius(SurfaceControl surfaceControl, float radius) {
- mTransaction.setCornerRadius(surfaceControl, radius);
- return this;
- }
-
- public TransactionCompat setBackgroundBlurRadius(SurfaceControl surfaceControl, int radius) {
- mTransaction.setBackgroundBlurRadius(surfaceControl, radius);
- return this;
- }
-
- public TransactionCompat setColor(SurfaceControl surfaceControl, float[] color) {
- mTransaction.setColor(surfaceControl, color);
- return this;
- }
-
- public static void setRelativeLayer(Transaction t, SurfaceControl surfaceControl,
- SurfaceControl relativeTo, int z) {
- t.setRelativeLayer(surfaceControl, relativeTo, z);
- }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
deleted file mode 100644
index 6064be94bc0a..000000000000
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ /dev/null
@@ -1,302 +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.keyguard;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.icu.text.NumberFormat;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settingslib.Utils;
-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.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.clocks.AnimatableClockView;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.util.ViewController;
-
-import java.io.PrintWriter;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.TimeZone;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-/**
- * Controller for an AnimatableClockView on the keyguard. Instantiated by
- * {@link KeyguardClockSwitchController}.
- */
-public class AnimatableClockController extends ViewController<AnimatableClockView> {
- private static final String TAG = "AnimatableClockCtrl";
- private static final int FORMAT_NUMBER = 1234567890;
-
- private final StatusBarStateController mStatusBarStateController;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final BatteryController mBatteryController;
- private final int mDozingColor = Color.WHITE;
- private Optional<RegionSamplingHelper> mRegionSamplingHelper = Optional.empty();
- private Rect mSamplingBounds = new Rect();
- private int mLockScreenColor;
- private final boolean mRegionSamplingEnabled;
-
- private boolean mIsDozing;
- private boolean mIsCharging;
- private float mDozeAmount;
- boolean mKeyguardShowing;
- private Locale mLocale;
-
- private final NumberFormat mBurmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"));
- private final String mBurmeseNumerals;
- private final float mBurmeseLineSpacing;
- private final float mDefaultLineSpacing;
-
- @Inject
- public AnimatableClockController(
- AnimatableClockView view,
- StatusBarStateController statusBarStateController,
- BroadcastDispatcher broadcastDispatcher,
- BatteryController batteryController,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- @Main Resources resources,
- @Main Executor mainExecutor,
- @Background Executor bgExecutor,
- FeatureFlags featureFlags
- ) {
- super(view);
- mStatusBarStateController = statusBarStateController;
- mBroadcastDispatcher = broadcastDispatcher;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mBatteryController = batteryController;
-
- mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
- mBurmeseLineSpacing = resources.getFloat(
- R.dimen.keyguard_clock_line_spacing_scale_burmese);
- mDefaultLineSpacing = resources.getFloat(
- R.dimen.keyguard_clock_line_spacing_scale);
-
- mRegionSamplingEnabled = featureFlags.isEnabled(Flags.REGION_SAMPLING);
- if (!mRegionSamplingEnabled) {
- return;
- }
-
- mRegionSamplingHelper = Optional.of(new RegionSamplingHelper(mView,
- new RegionSamplingHelper.SamplingCallback() {
- @Override
- public void onRegionDarknessChanged(boolean isRegionDark) {
- if (isRegionDark) {
- mLockScreenColor = Color.WHITE;
- } else {
- mLockScreenColor = Color.BLACK;
- }
- initColors();
- }
-
- @Override
- public Rect getSampledRegion(View sampledView) {
- mSamplingBounds = new Rect(sampledView.getLeft(), sampledView.getTop(),
- sampledView.getRight(), sampledView.getBottom());
- return mSamplingBounds;
- }
-
- @Override
- public boolean isSamplingEnabled() {
- return mRegionSamplingEnabled;
- }
- }, mainExecutor, bgExecutor)
- );
- mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
- regionSamplingHelper.setWindowVisible(true);
- });
- }
-
- private void reset() {
- mView.animateDoze(mIsDozing, false);
- }
-
- private final BatteryController.BatteryStateChangeCallback mBatteryCallback =
- new BatteryController.BatteryStateChangeCallback() {
- @Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- if (mKeyguardShowing && !mIsCharging && charging) {
- mView.animateCharge(mStatusBarStateController::isDozing);
- }
- mIsCharging = charging;
- }
- };
-
- private final BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- updateLocale();
- }
- };
-
- private final StatusBarStateController.StateListener mStatusBarStateListener =
- new StatusBarStateController.StateListener() {
- @Override
- public void onDozeAmountChanged(float linear, float eased) {
- boolean noAnimation = (mDozeAmount == 0f && linear == 1f)
- || (mDozeAmount == 1f && linear == 0f);
- boolean isDozing = linear > mDozeAmount;
- mDozeAmount = linear;
- if (mIsDozing != isDozing) {
- mIsDozing = isDozing;
- mView.animateDoze(mIsDozing, !noAnimation);
- }
- }
- };
-
- private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mKeyguardShowing = showing;
- if (!mKeyguardShowing) {
- // reset state (ie: after weight animations)
- 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
- protected void onInit() {
- mIsDozing = mStatusBarStateController.isDozing();
- }
-
- @Override
- protected void onViewAttached() {
- updateLocale();
- mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
- new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
- mDozeAmount = mStatusBarStateController.getDozeAmount();
- mIsDozing = mStatusBarStateController.isDozing() || mDozeAmount != 0;
- mBatteryController.addCallback(mBatteryCallback);
- mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
-
- mStatusBarStateController.addCallback(mStatusBarStateListener);
-
- mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
- regionSamplingHelper.start(mSamplingBounds);
- });
-
- mView.onTimeZoneChanged(TimeZone.getDefault());
- initColors();
- mView.animateDoze(mIsDozing, false);
- }
-
- @Override
- protected void onViewDetached() {
- mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
- mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
- mBatteryController.removeCallback(mBatteryCallback);
- mStatusBarStateController.removeCallback(mStatusBarStateListener);
- mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
- regionSamplingHelper.stop();
- });
- }
-
- /** Animate the clock appearance */
- public void animateAppear() {
- if (!mIsDozing) mView.animateAppearOnLockscreen();
- }
-
- /** 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(true);
- }
-
- /**
- * Updates the time for the view.
- */
- public void refreshTime() {
- mView.refreshTime();
- }
-
- /**
- * Return locallly stored dozing state.
- */
- @VisibleForTesting
- public boolean isDozing() {
- return mIsDozing;
- }
-
- private void updateLocale() {
- Locale currLocale = Locale.getDefault();
- if (!Objects.equals(currLocale, mLocale)) {
- mLocale = currLocale;
- NumberFormat nf = NumberFormat.getInstance(mLocale);
- if (nf.format(FORMAT_NUMBER).equals(mBurmeseNumerals)) {
- mView.setLineSpacingScale(mBurmeseLineSpacing);
- } else {
- mView.setLineSpacingScale(mDefaultLineSpacing);
- }
- mView.refreshFormat();
- }
- }
-
- private void initColors() {
- if (!mRegionSamplingEnabled) {
- mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
- com.android.systemui.R.attr.wallpaperTextColorAccent);
- }
- mView.setColors(mDozingColor, mLockScreenColor);
- mView.animateDoze(mIsDozing, false);
- }
-
- /**
- * Dump information for debugging
- */
- public void dump(@NonNull PrintWriter pw) {
- pw.println(this);
- mView.dump(pw);
- mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
- regionSamplingHelper.dump(pw);
- });
- }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 0075ddd73cd3..450784ea8f03 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -16,19 +16,29 @@
package com.android.keyguard
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.TypedArray
import android.graphics.Color
import android.util.AttributeSet
+import android.view.View
import com.android.settingslib.Utils
+import com.android.systemui.animation.Interpolators
/** Displays security messages for the keyguard bouncer. */
-class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
+open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
KeyguardMessageArea(context, attrs) {
private val DEFAULT_COLOR = -1
private var mDefaultColorState: ColorStateList? = null
private var mNextMessageColorState: ColorStateList? = ColorStateList.valueOf(DEFAULT_COLOR)
+ private val animatorSet = AnimatorSet()
+ private var textAboutToShow: CharSequence? = null
+ protected open val SHOW_DURATION_MILLIS = 150L
+ protected open val HIDE_DURATION_MILLIS = 200L
override fun updateTextColor() {
var colorState = mDefaultColorState
@@ -58,4 +68,46 @@ class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
mDefaultColorState = Utils.getColorAttr(context, android.R.attr.textColorPrimary)
super.reloadColor()
}
+
+ override fun setMessage(msg: CharSequence?) {
+ if ((msg == textAboutToShow && msg != null) || msg == text) {
+ return
+ }
+ textAboutToShow = msg
+
+ if (animatorSet.isRunning) {
+ animatorSet.cancel()
+ textAboutToShow = null
+ }
+
+ val hideAnimator =
+ ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f).apply {
+ duration = HIDE_DURATION_MILLIS
+ interpolator = Interpolators.STANDARD_ACCELERATE
+ }
+
+ hideAnimator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ super@BouncerKeyguardMessageArea.setMessage(msg)
+ }
+ }
+ )
+ val showAnimator =
+ ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f).apply {
+ duration = SHOW_DURATION_MILLIS
+ interpolator = Interpolators.STANDARD_DECELERATE
+ }
+
+ showAnimator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ textAboutToShow = null
+ }
+ }
+ )
+
+ animatorSet.playSequentially(hideAnimator, showAnimator)
+ animatorSet.start()
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 9151238499dc..386c09575a1a 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -23,12 +23,20 @@ import android.content.res.Resources
import android.text.format.DateFormat
import android.util.TypedValue
import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.REGION_SAMPLING
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.dagger.KeyguardClockLog
import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.shared.regionsampling.RegionSamplingInstance
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
@@ -38,13 +46,20 @@ import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
/**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
* [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
*/
open class ClockEventController @Inject constructor(
- private val statusBarStateController: StatusBarStateController,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val broadcastDispatcher: BroadcastDispatcher,
private val batteryController: BatteryController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -53,12 +68,14 @@ open class ClockEventController @Inject constructor(
private val context: Context,
@Main private val mainExecutor: Executor,
@Background private val bgExecutor: Executor,
- private val featureFlags: FeatureFlags,
+ @KeyguardClockLog private val logBuffer: LogBuffer,
+ private val featureFlags: FeatureFlags
) {
var clock: ClockController? = null
set(value) {
field = value
if (value != null) {
+ value.setLogBuffer(logBuffer)
value.initialize(resources, dozeAmount, 0f)
updateRegionSamplers(value)
}
@@ -70,9 +87,9 @@ open class ClockEventController @Inject constructor(
private var isCharging = false
private var dozeAmount = 0f
private var isKeyguardVisible = false
-
- private val regionSamplingEnabled =
- featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING)
+ private var isRegistered = false
+ private var disposableHandle: DisposableHandle? = null
+ private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
private fun updateColors() {
if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
@@ -165,15 +182,6 @@ open class ClockEventController @Inject constructor(
}
}
- private val statusBarStateListener = object : StatusBarStateController.StateListener {
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- clock?.animations?.doze(linear)
-
- isDozing = linear > dozeAmount
- dozeAmount = linear
- }
- }
-
private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
override fun onKeyguardVisibilityChanged(visible: Boolean) {
isKeyguardVisible = visible
@@ -195,13 +203,11 @@ open class ClockEventController @Inject constructor(
}
}
- init {
- isDozing = statusBarStateController.isDozing
- }
-
- fun registerListeners() {
- dozeAmount = statusBarStateController.dozeAmount
- isDozing = statusBarStateController.isDozing || dozeAmount != 0f
+ fun registerListeners(parent: View) {
+ if (isRegistered) {
+ return
+ }
+ isRegistered = true
broadcastDispatcher.registerReceiver(
localeBroadcastReceiver,
@@ -210,17 +216,28 @@ open class ClockEventController @Inject constructor(
configurationController.addCallback(configListener)
batteryController.addCallback(batteryCallback)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
- statusBarStateController.addCallback(statusBarStateListener)
smallRegionSampler?.startRegionSampler()
largeRegionSampler?.startRegionSampler()
+ disposableHandle = parent.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ listenForDozing(this)
+ listenForDozeAmount(this)
+ listenForDozeAmountTransition(this)
+ }
+ }
}
fun unregisterListeners() {
+ if (!isRegistered) {
+ return
+ }
+ isRegistered = false
+
+ disposableHandle?.dispose()
broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
configurationController.removeCallback(configListener)
batteryController.removeCallback(batteryCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
- statusBarStateController.removeCallback(statusBarStateListener)
smallRegionSampler?.stopRegionSampler()
largeRegionSampler?.stopRegionSampler()
}
@@ -235,8 +252,39 @@ open class ClockEventController @Inject constructor(
largeRegionSampler?.dump(pw)
}
- companion object {
- private val TAG = ClockEventController::class.simpleName
- private const val FORMAT_NUMBER = 1234567890
+ @VisibleForTesting
+ internal fun listenForDozeAmount(scope: CoroutineScope): Job {
+ return scope.launch {
+ keyguardInteractor.dozeAmount.collect {
+ dozeAmount = it
+ clock?.animations?.doze(dozeAmount)
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
+ return scope.launch {
+ keyguardTransitionInteractor.aodToLockscreenTransition.collect {
+ // Would eventually run this:
+ // dozeAmount = it.value
+ // clock?.animations?.doze(dozeAmount)
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal fun listenForDozing(scope: CoroutineScope): Job {
+ return scope.launch {
+ combine (
+ keyguardInteractor.dozeAmount,
+ keyguardInteractor.isDozing,
+ ) { localDozeAmount, localIsDozing ->
+ localDozeAmount > dozeAmount || localIsDozing
+ }
+ .collect { localIsDozing ->
+ isDozing = localIsDozing
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index d03ef984d42c..8ebad6c0fdbf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -127,7 +127,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
if (useLargeClock) {
out = mSmallClockFrame;
in = mLargeClockFrame;
- if (indexOfChild(in) == -1) addView(in);
+ if (indexOfChild(in) == -1) addView(in, 0);
direction = -1;
statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop()
+ mSmartspaceTopOffset;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index b450ec35db32..35eecdf27f48 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -40,6 +40,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.ClockRegistry;
@@ -164,7 +165,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
protected void onViewAttached() {
mClockRegistry.registerClockChangeListener(mClockChangedListener);
setClock(mClockRegistry.createCurrentClock());
- mClockEventController.registerListeners();
+ mClockEventController.registerListeners(mView);
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
@@ -404,5 +405,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
clock.dump(pw);
}
}
+
+ /** Gets the animations for the current clock. */
+ public ClockAnimations getClockAnimations() {
+ return getClock().getAnimations();
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index f26b9057dc7c..73229c321079 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -152,6 +152,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
}
public void startAppearAnimation() {
+ mMessageAreaController.setMessage(getInitialMessageResId());
mView.startAppearAnimation();
}
@@ -169,6 +170,11 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
return view.indexOfChild(mView);
}
+ /** Determines the message to show in the bouncer when it first appears. */
+ protected int getInitialMessageResId() {
+ return 0;
+ }
+
/** Factory for a {@link KeyguardInputViewController}. */
public static class Factory {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index c2802f7b6843..2bd3ca59b740 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -18,7 +18,6 @@ package com.android.keyguard;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
-import android.text.TextUtils;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -100,15 +99,6 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
mView.setMessage(resId);
}
- /**
- * Set Text if KeyguardMessageArea is empty.
- */
- public void setMessageIfEmpty(int resId) {
- if (TextUtils.isEmpty(mView.getText())) {
- setMessage(resId);
- }
- }
-
public void setNextMessageColor(ColorStateList colorState) {
mView.setNextMessageColor(colorState);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 29e912fdab32..0025986c0e5c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -187,7 +187,7 @@ public class KeyguardPasswordViewController
@Override
void resetState() {
mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
- mMessageAreaController.setMessage("");
+ mMessageAreaController.setMessage(getInitialMessageResId());
final boolean wasDisabled = mPasswordEntry.isEnabled();
mView.setPasswordEntryEnabled(true);
mView.setPasswordEntryInputEnabled(true);
@@ -207,7 +207,6 @@ public class KeyguardPasswordViewController
if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
showInput();
}
- mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_password);
}
private void showInput() {
@@ -324,4 +323,9 @@ public class KeyguardPasswordViewController
//enabled input method subtype (The current IME should be LatinIME.)
|| imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
}
+
+ @Override
+ protected int getInitialMessageResId() {
+ return R.string.keyguard_enter_your_password;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 987164557a7a..1f0bd54f8e09 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -298,12 +298,6 @@ public class KeyguardPatternViewController
}
@Override
- public void onResume(int reason) {
- super.onResume(reason);
- mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pattern);
- }
-
- @Override
public boolean needsInput() {
return false;
}
@@ -361,7 +355,7 @@ public class KeyguardPatternViewController
}
private void displayDefaultSecurityMessage() {
- mMessageAreaController.setMessage("");
+ mMessageAreaController.setMessage(getInitialMessageResId());
}
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
@@ -392,4 +386,9 @@ public class KeyguardPatternViewController
}.start();
}
+
+ @Override
+ protected int getInitialMessageResId() {
+ return R.string.keyguard_enter_your_pattern;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 59a018ad51df..f7423ed12e68 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -127,7 +127,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
public void onResume(int reason) {
super.onResume(reason);
mPasswordEntry.requestFocus();
- mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 89fcc47caf57..7876f071fdf5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -76,20 +76,13 @@ public class KeyguardPinViewController
}
@Override
- void resetState() {
- super.resetState();
- mMessageAreaController.setMessage("");
- }
-
- @Override
- public void startAppearAnimation() {
- mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
- super.startAppearAnimation();
- }
-
- @Override
public boolean startDisappearAnimation(Runnable finishRunnable) {
return mView.startDisappearAnimation(
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
}
+
+ @Override
+ protected int getInitialMessageResId() {
+ return R.string.keyguard_enter_your_pin;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index bcd1a1ee2696..81305f90e2b8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -219,13 +219,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
};
- private SwipeListener mSwipeListener = new SwipeListener() {
+ private final SwipeListener mSwipeListener = new SwipeListener() {
@Override
public void onSwipeUp() {
if (!mUpdateMonitor.isFaceDetectionRunning()) {
- mUpdateMonitor.requestFaceAuth(true, FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+ boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+ FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
mKeyguardSecurityCallback.userActivity();
- showMessage(null, null);
+ if (didFaceAuthRun) {
+ showMessage(null, null);
+ }
}
if (mUpdateMonitor.isFaceEnrolled()) {
mUpdateMonitor.requestActiveUnlock(
@@ -234,7 +237,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
}
};
- private ConfigurationController.ConfigurationListener mConfigurationListener =
+ private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
public void onThemeChanged() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index e9f06eddf261..784974778af5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -20,6 +20,7 @@ import android.graphics.Rect;
import android.util.Slog;
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
+import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -232,4 +233,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
mView.setClipBounds(null);
}
}
+
+ /** Gets the animations for the current clock. */
+ public ClockAnimations getClockAnimations() {
+ return mKeyguardClockSwitchController.getClockAnimations();
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8792a211ad5b..cb1330dbd53d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1428,6 +1428,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
+ private void notifyNonStrongBiometricStateChanged(int userId) {
+ Assert.isMainThread();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onNonStrongBiometricAllowedChanged(userId);
+ }
+ }
+ }
+
private void dispatchErrorMessage(CharSequence message) {
Assert.isMainThread();
for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1778,11 +1788,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
public static class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
private final Consumer<Integer> mStrongAuthRequiredChangedCallback;
+ private final Consumer<Integer> mNonStrongBiometricAllowedChanged;
public StrongAuthTracker(Context context,
- Consumer<Integer> strongAuthRequiredChangedCallback) {
+ Consumer<Integer> strongAuthRequiredChangedCallback,
+ Consumer<Integer> nonStrongBiometricAllowedChanged) {
super(context);
mStrongAuthRequiredChangedCallback = strongAuthRequiredChangedCallback;
+ mNonStrongBiometricAllowedChanged = nonStrongBiometricAllowedChanged;
}
public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
@@ -1800,6 +1813,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
public void onStrongAuthRequiredChanged(int userId) {
mStrongAuthRequiredChangedCallback.accept(userId);
}
+
+ // TODO(b/247091681): Renaming the inappropriate onIsNonStrongBiometricAllowedChanged
+ // callback wording for Weak/Convenience idle timeout constraint that only allow
+ // Strong-Auth
+ @Override
+ public void onIsNonStrongBiometricAllowedChanged(int userId) {
+ mNonStrongBiometricAllowedChanged.accept(userId);
+ }
}
protected void handleStartedWakingUp() {
@@ -1948,7 +1969,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mSubscriptionManager = subscriptionManager;
mTelephonyListenerManager = telephonyListenerManager;
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
- mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
+ mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged,
+ this::notifyNonStrongBiometricStateChanged);
mBackgroundExecutor = backgroundExecutor;
mBroadcastDispatcher = broadcastDispatcher;
mInteractionJankMonitor = interactionJankMonitor;
@@ -2344,11 +2366,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* @param userInitiatedRequest true if the user explicitly requested face auth
* @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
* invoked.
+ * @return current face auth detection state, true if it is running.
*/
- public void requestFaceAuth(boolean userInitiatedRequest,
+ public boolean requestFaceAuth(boolean userInitiatedRequest,
@FaceAuthApiRequestReason String reason) {
mLogger.logFaceAuthRequested(userInitiatedRequest, reason);
updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
+ return isFaceDetectionRunning();
}
/**
@@ -2358,10 +2382,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
stopListeningForFace(FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER);
}
- public boolean isFaceScanning() {
- return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
- }
-
private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) {
// If this message exists, we should not authenticate again until this message is
// consumed by the handler
@@ -2409,7 +2429,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* Attempts to trigger active unlock from trust agent.
*/
private void requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+ @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
String reason,
boolean dismissKeyguard
) {
@@ -2439,7 +2459,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* Only dismisses the keyguard under certain conditions.
*/
public void requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+ @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
String extraReason
) {
final boolean canFaceBypass = isFaceEnrolled() && mKeyguardBypassController != null
@@ -2706,7 +2726,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return shouldListen;
}
- private void maybeLogListenerModelData(KeyguardListenModel model) {
+ private void maybeLogListenerModelData(@NonNull KeyguardListenModel model) {
mLogger.logKeyguardListenerModel(model);
if (model instanceof KeyguardActiveUnlockModel) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index bc5ab88b586b..c06e1dcf08c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -291,4 +291,9 @@ public class KeyguardUpdateMonitorCallback {
* Called when the notification shade is expanded or collapsed.
*/
public void onShadeExpandedChanged(boolean expanded) { }
+
+ /**
+ * Called when the non-strong biometric state changed.
+ */
+ public void onNonStrongBiometricAllowedChanged(int userId) { }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
index c4be1ba53503..72a44bd198f2 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
@@ -21,9 +21,14 @@ import java.util.List;
import dagger.Module;
import dagger.Provides;
-/** Dagger Module for clock package. */
+/**
+ * Dagger Module for clock package.
+ *
+ * @deprecated Migrate to ClockRegistry
+ */
@Module
-public abstract class ClockModule {
+@Deprecated
+public abstract class ClockInfoModule {
/** */
@Provides
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
new file mode 100644
index 000000000000..f43f559b4234
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard.dagger;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.shared.clocks.ClockRegistry;
+import com.android.systemui.shared.clocks.DefaultClockProvider;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger Module for clocks. */
+@Module
+public abstract class ClockRegistryModule {
+ /** Provide the ClockRegistry as a singleton so that it is not instantiated more than once. */
+ @Provides
+ @SysUISingleton
+ public static ClockRegistry getClockRegistry(
+ @Application Context context,
+ PluginManager pluginManager,
+ @Main Handler handler,
+ DefaultClockProvider defaultClockProvider) {
+ return new ClockRegistry(context, pluginManager, handler, defaultClockProvider);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
index 2c2ab7b39161..6264ce7273f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -17,9 +17,9 @@
package com.android.keyguard.logging
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.dagger.BiometricMessagesLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
import javax.inject.Inject
/** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 50012a589b5a..46f3d4e5f6aa 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -16,15 +16,15 @@
package com.android.keyguard.logging
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogLevel.WARNING
-import com.android.systemui.log.MessageInitializer
-import com.android.systemui.log.MessagePrinter
import com.android.systemui.log.dagger.KeyguardLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.plugins.log.MessageInitializer
+import com.android.systemui.plugins.log.MessagePrinter
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 2eee95738b7b..2f79e30a0b5b 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -22,13 +22,13 @@ import android.telephony.SubscriptionInfo
import com.android.keyguard.ActiveUnlockConfig
import com.android.keyguard.KeyguardListenModel
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogLevel.WARNING
import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
@@ -51,7 +51,7 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
- fun logActiveUnlockTriggered(reason: String) {
+ fun logActiveUnlockTriggered(reason: String?) {
logBuffer.log("ActiveUnlock", DEBUG,
{ str1 = reason },
{ "initiate active unlock triggerReason=$str1" })
@@ -101,14 +101,14 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
{ "Face authenticated for wrong user: $int1" })
}
- fun logFaceAuthHelpMsg(msgId: Int, helpMsg: String) {
+ fun logFaceAuthHelpMsg(msgId: Int, helpMsg: String?) {
logBuffer.log(TAG, DEBUG, {
int1 = msgId
str1 = helpMsg
}, { "Face help received, msgId: $int1 msg: $str1" })
}
- fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String) {
+ fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String?) {
logBuffer.log(TAG, DEBUG, {
bool1 = userInitiatedRequest
str1 = reason
@@ -187,7 +187,7 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
{ "No Profile Owner or Device Owner supervision app found for User $int1" })
}
- fun logPhoneStateChanged(newState: String) {
+ fun logPhoneStateChanged(newState: String?) {
logBuffer.log(TAG, DEBUG,
{ str1 = newState },
{ "handlePhoneStateChanged($str1)" })
@@ -240,7 +240,7 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
}, { "handleServiceStateChange(subId=$int1, serviceState=$str1)" })
}
- fun logServiceStateIntent(action: String, serviceState: ServiceState?, subId: Int) {
+ fun logServiceStateIntent(action: String?, serviceState: ServiceState?, subId: Int) {
logBuffer.log(TAG, VERBOSE, {
str1 = action
str2 = "$serviceState"
@@ -256,7 +256,7 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
}, { "handleSimStateChange(subId=$int1, slotId=$int2, state=$long1)" })
}
- fun logSimStateFromIntent(action: String, extraSimState: String, slotId: Int, subId: Int) {
+ fun logSimStateFromIntent(action: String?, extraSimState: String?, slotId: Int, subId: Int) {
logBuffer.log(TAG, VERBOSE, {
str1 = action
str2 = extraSimState
@@ -289,7 +289,7 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
{ "SubInfo:$str1" })
}
- fun logTimeFormatChanged(newTimeFormat: String) {
+ fun logTimeFormatChanged(newTimeFormat: String?) {
logBuffer.log(TAG, DEBUG,
{ str1 = newTimeFormat },
{ "handleTimeFormatUpdate timeFormat=$str1" })
@@ -338,18 +338,18 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
fun logUserRequestedUnlock(
requestOrigin: ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN,
- reason: String,
+ reason: String?,
dismissKeyguard: Boolean
) {
logBuffer.log("ActiveUnlock", DEBUG, {
- str1 = requestOrigin.name
+ str1 = requestOrigin?.name
str2 = reason
bool1 = dismissKeyguard
}, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" })
}
fun logShowTrustGrantedMessage(
- message: String
+ message: String?
) {
logBuffer.log(TAG, DEBUG, {
str1 = message
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index a3351e1a6440..5d52056d8b17 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -86,30 +86,38 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView {
onUpdate()
}
- fun onDisplayChanged(newDisplayUniqueId: String?) {
+ fun updateConfiguration(newDisplayUniqueId: String?) {
+ val info = DisplayInfo()
+ context.display?.getDisplayInfo(info)
val oldMode: Display.Mode? = displayMode
- val display: Display? = context.display
- displayMode = display?.mode
+ displayMode = info.mode
- if (displayUniqueId != display?.uniqueId) {
- displayUniqueId = display?.uniqueId
- shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout(
- context.resources, displayUniqueId
- )
- }
+ updateDisplayUniqueId(info.uniqueId)
// Skip if display mode or cutout hasn't changed.
if (!displayModeChanged(oldMode, displayMode) &&
- display?.cutout == displayInfo.displayCutout) {
+ displayInfo.displayCutout == info.displayCutout &&
+ displayRotation == info.rotation) {
return
}
- if (newDisplayUniqueId == display?.uniqueId) {
+ if (newDisplayUniqueId == info.uniqueId) {
+ displayRotation = info.rotation
updateCutout()
updateProtectionBoundingPath()
onUpdate()
}
}
+ open fun updateDisplayUniqueId(newDisplayUniqueId: String?) {
+ if (displayUniqueId != newDisplayUniqueId) {
+ displayUniqueId = newDisplayUniqueId
+ shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout(
+ context.resources, displayUniqueId
+ )
+ invalidate()
+ }
+ }
+
open fun updateRotation(rotation: Int) {
displayRotation = rotation
updateCutout()
diff --git a/packages/SystemUI/src/com/android/systemui/Dumpable.java b/packages/SystemUI/src/com/android/systemui/Dumpable.java
index 652595100c0f..73fdce6c9045 100644
--- a/packages/SystemUI/src/com/android/systemui/Dumpable.java
+++ b/packages/SystemUI/src/com/android/systemui/Dumpable.java
@@ -30,7 +30,6 @@ public interface Dumpable {
/**
* Called when it's time to dump the internal state
- * @param fd A file descriptor.
* @param pw Where to write your dump to.
* @param args Arguments.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index c5955860aebf..3e0fa455d39e 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -19,6 +19,7 @@ package com.android.systemui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
+import android.animation.TimeInterpolator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
@@ -55,7 +56,7 @@ class FaceScanningOverlay(
private val rimRect = RectF()
private var cameraProtectionColor = Color.BLACK
var faceScanningAnimColor = Utils.getColorAttrDefaultColor(context,
- com.android.systemui.R.attr.wallpaperTextColorAccent)
+ R.attr.wallpaperTextColorAccent)
private var cameraProtectionAnimator: ValueAnimator? = null
var hideOverlayRunnable: Runnable? = null
var faceAuthSucceeded = false
@@ -84,46 +85,19 @@ class FaceScanningOverlay(
}
override fun drawCutoutProtection(canvas: Canvas) {
- if (rimProgress > HIDDEN_RIM_SCALE && !protectionRect.isEmpty) {
- val rimPath = Path(protectionPath)
- val scaleMatrix = Matrix().apply {
- val rimBounds = RectF()
- rimPath.computeBounds(rimBounds, true)
- setScale(rimProgress, rimProgress, rimBounds.centerX(), rimBounds.centerY())
- }
- rimPath.transform(scaleMatrix)
- rimPaint.style = Paint.Style.FILL
- val rimPaintAlpha = rimPaint.alpha
- rimPaint.color = ColorUtils.blendARGB(
- faceScanningAnimColor,
- Color.WHITE,
- statusBarStateController.dozeAmount)
- rimPaint.alpha = rimPaintAlpha
- canvas.drawPath(rimPath, rimPaint)
+ if (protectionRect.isEmpty) {
+ return
}
-
- if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE &&
- !protectionRect.isEmpty) {
- val scaledProtectionPath = Path(protectionPath)
- val scaleMatrix = Matrix().apply {
- val protectionPathRect = RectF()
- scaledProtectionPath.computeBounds(protectionPathRect, true)
- setScale(cameraProtectionProgress, cameraProtectionProgress,
- protectionPathRect.centerX(), protectionPathRect.centerY())
- }
- scaledProtectionPath.transform(scaleMatrix)
- paint.style = Paint.Style.FILL
- paint.color = cameraProtectionColor
- canvas.drawPath(scaledProtectionPath, paint)
+ if (rimProgress > HIDDEN_RIM_SCALE) {
+ drawFaceScanningRim(canvas)
+ }
+ if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE) {
+ drawCameraProtection(canvas)
}
- }
-
- override fun updateVisOnUpdateCutout(): Boolean {
- return false // instead, we always update the visibility whenever face scanning starts/ends
}
override fun enableShowProtection(show: Boolean) {
- val showScanningAnimNow = keyguardUpdateMonitor.isFaceScanning && show
+ val showScanningAnimNow = keyguardUpdateMonitor.isFaceDetectionRunning && show
if (showScanningAnimNow == showScanningAnim) {
return
}
@@ -152,91 +126,26 @@ class FaceScanningOverlay(
if (showScanningAnim) Interpolators.STANDARD_ACCELERATE
else if (faceAuthSucceeded) Interpolators.STANDARD
else Interpolators.STANDARD_DECELERATE
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- cameraProtectionProgress = animation.animatedValue as Float
- invalidate()
- })
+ addUpdateListener(this@FaceScanningOverlay::updateCameraProtectionProgress)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
cameraProtectionAnimator = null
if (!showScanningAnim) {
- visibility = View.INVISIBLE
- hideOverlayRunnable?.run()
- hideOverlayRunnable = null
- requestLayout()
+ hide()
}
}
})
}
rimAnimator?.cancel()
- rimAnimator = AnimatorSet().apply {
- if (showScanningAnim) {
- val rimAppearAnimator = ValueAnimator.ofFloat(SHOW_CAMERA_PROTECTION_SCALE,
- PULSE_RADIUS_OUT).apply {
- duration = PULSE_APPEAR_DURATION
- interpolator = Interpolators.STANDARD_DECELERATE
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- rimProgress = animation.animatedValue as Float
- invalidate()
- })
- }
-
- // animate in camera protection, rim, and then pulse in/out
- playSequentially(cameraProtectionAnimator, rimAppearAnimator,
- createPulseAnimator(), createPulseAnimator(),
- createPulseAnimator(), createPulseAnimator(),
- createPulseAnimator(), createPulseAnimator())
- } else {
- val rimDisappearAnimator = ValueAnimator.ofFloat(
- rimProgress,
- if (faceAuthSucceeded) PULSE_RADIUS_SUCCESS
- else SHOW_CAMERA_PROTECTION_SCALE
- ).apply {
- duration =
- if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION
- else PULSE_ERROR_DISAPPEAR_DURATION
- interpolator =
- if (faceAuthSucceeded) Interpolators.STANDARD_DECELERATE
- else Interpolators.STANDARD
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- rimProgress = animation.animatedValue as Float
- invalidate()
- })
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- rimProgress = HIDDEN_RIM_SCALE
- invalidate()
- }
- })
- }
- if (faceAuthSucceeded) {
- val successOpacityAnimator = ValueAnimator.ofInt(255, 0).apply {
- duration = PULSE_SUCCESS_DISAPPEAR_DURATION
- interpolator = Interpolators.LINEAR
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- rimPaint.alpha = animation.animatedValue as Int
- invalidate()
- })
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- rimPaint.alpha = 255
- invalidate()
- }
- })
- }
- val rimSuccessAnimator = AnimatorSet()
- rimSuccessAnimator.playTogether(rimDisappearAnimator, successOpacityAnimator)
- playTogether(rimSuccessAnimator, cameraProtectionAnimator)
- } else {
- playTogether(rimDisappearAnimator, cameraProtectionAnimator)
- }
- }
-
+ rimAnimator = if (showScanningAnim) {
+ createFaceScanningRimAnimator()
+ } else if (faceAuthSucceeded) {
+ createFaceSuccessRimAnimator()
+ } else {
+ createFaceNotSuccessRimAnimator()
+ }
+ rimAnimator?.apply {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
rimAnimator = null
@@ -245,34 +154,12 @@ class FaceScanningOverlay(
}
}
})
- start()
}
+ rimAnimator?.start()
}
- fun createPulseAnimator(): AnimatorSet {
- return AnimatorSet().apply {
- val pulseInwards = ValueAnimator.ofFloat(
- PULSE_RADIUS_OUT, PULSE_RADIUS_IN).apply {
- duration = PULSE_DURATION_INWARDS
- interpolator = Interpolators.STANDARD
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- rimProgress = animation.animatedValue as Float
- invalidate()
- })
- }
- val pulseOutwards = ValueAnimator.ofFloat(
- PULSE_RADIUS_IN, PULSE_RADIUS_OUT).apply {
- duration = PULSE_DURATION_OUTWARDS
- interpolator = Interpolators.STANDARD
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- rimProgress = animation.animatedValue as Float
- invalidate()
- })
- }
- playSequentially(pulseInwards, pulseOutwards)
- }
+ override fun updateVisOnUpdateCutout(): Boolean {
+ return false // instead, we always update the visibility whenever face scanning starts/ends
}
override fun updateProtectionBoundingPath() {
@@ -290,17 +177,153 @@ class FaceScanningOverlay(
// Make sure that our measured height encompasses the extra space for the animation
mTotalBounds.union(mBoundingRect)
mTotalBounds.union(
- rimRect.left.toInt(),
- rimRect.top.toInt(),
- rimRect.right.toInt(),
- rimRect.bottom.toInt())
+ rimRect.left.toInt(),
+ rimRect.top.toInt(),
+ rimRect.right.toInt(),
+ rimRect.bottom.toInt())
setMeasuredDimension(
- resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
- resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0))
+ resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
+ resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0))
} else {
setMeasuredDimension(
- resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
- resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0))
+ resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
+ resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0))
+ }
+ }
+
+ private fun drawFaceScanningRim(canvas: Canvas) {
+ val rimPath = Path(protectionPath)
+ scalePath(rimPath, rimProgress)
+ rimPaint.style = Paint.Style.FILL
+ val rimPaintAlpha = rimPaint.alpha
+ rimPaint.color = ColorUtils.blendARGB(
+ faceScanningAnimColor,
+ Color.WHITE,
+ statusBarStateController.dozeAmount
+ )
+ rimPaint.alpha = rimPaintAlpha
+ canvas.drawPath(rimPath, rimPaint)
+ }
+
+ private fun drawCameraProtection(canvas: Canvas) {
+ val scaledProtectionPath = Path(protectionPath)
+ scalePath(scaledProtectionPath, cameraProtectionProgress)
+ paint.style = Paint.Style.FILL
+ paint.color = cameraProtectionColor
+ canvas.drawPath(scaledProtectionPath, paint)
+ }
+
+ private fun createFaceSuccessRimAnimator(): AnimatorSet {
+ val rimSuccessAnimator = AnimatorSet()
+ rimSuccessAnimator.playTogether(
+ createRimDisappearAnimator(
+ PULSE_RADIUS_SUCCESS,
+ PULSE_SUCCESS_DISAPPEAR_DURATION,
+ Interpolators.STANDARD_DECELERATE
+ ),
+ createSuccessOpacityAnimator(),
+ )
+ return AnimatorSet().apply {
+ playTogether(rimSuccessAnimator, cameraProtectionAnimator)
+ }
+ }
+
+ private fun createFaceNotSuccessRimAnimator(): AnimatorSet {
+ return AnimatorSet().apply {
+ playTogether(
+ createRimDisappearAnimator(
+ SHOW_CAMERA_PROTECTION_SCALE,
+ PULSE_ERROR_DISAPPEAR_DURATION,
+ Interpolators.STANDARD
+ ),
+ cameraProtectionAnimator,
+ )
+ }
+ }
+
+ private fun createRimDisappearAnimator(
+ endValue: Float,
+ animDuration: Long,
+ timeInterpolator: TimeInterpolator
+ ): ValueAnimator {
+ return ValueAnimator.ofFloat(rimProgress, endValue).apply {
+ duration = animDuration
+ interpolator = timeInterpolator
+ addUpdateListener(this@FaceScanningOverlay::updateRimProgress)
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ rimProgress = HIDDEN_RIM_SCALE
+ invalidate()
+ }
+ })
+ }
+ }
+
+ private fun createSuccessOpacityAnimator(): ValueAnimator {
+ return ValueAnimator.ofInt(255, 0).apply {
+ duration = PULSE_SUCCESS_DISAPPEAR_DURATION
+ interpolator = Interpolators.LINEAR
+ addUpdateListener(this@FaceScanningOverlay::updateRimAlpha)
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ rimPaint.alpha = 255
+ invalidate()
+ }
+ })
+ }
+ }
+
+ private fun createFaceScanningRimAnimator(): AnimatorSet {
+ return AnimatorSet().apply {
+ playSequentially(
+ cameraProtectionAnimator,
+ createRimAppearAnimator(),
+ createPulseAnimator()
+ )
+ }
+ }
+
+ private fun createRimAppearAnimator(): ValueAnimator {
+ return ValueAnimator.ofFloat(
+ SHOW_CAMERA_PROTECTION_SCALE,
+ PULSE_RADIUS_OUT
+ ).apply {
+ duration = PULSE_APPEAR_DURATION
+ interpolator = Interpolators.STANDARD_DECELERATE
+ addUpdateListener(this@FaceScanningOverlay::updateRimProgress)
+ }
+ }
+
+ private fun hide() {
+ visibility = INVISIBLE
+ hideOverlayRunnable?.run()
+ hideOverlayRunnable = null
+ requestLayout()
+ }
+
+ private fun updateRimProgress(animator: ValueAnimator) {
+ rimProgress = animator.animatedValue as Float
+ invalidate()
+ }
+
+ private fun updateCameraProtectionProgress(animator: ValueAnimator) {
+ cameraProtectionProgress = animator.animatedValue as Float
+ invalidate()
+ }
+
+ private fun updateRimAlpha(animator: ValueAnimator) {
+ rimPaint.alpha = animator.animatedValue as Int
+ invalidate()
+ }
+
+ private fun createPulseAnimator(): ValueAnimator {
+ return ValueAnimator.ofFloat(
+ PULSE_RADIUS_OUT, PULSE_RADIUS_IN).apply {
+ duration = HALF_PULSE_DURATION
+ interpolator = Interpolators.STANDARD
+ repeatCount = 11 // Pulse inwards and outwards, reversing direction, 6 times
+ repeatMode = ValueAnimator.REVERSE
+ addUpdateListener(this@FaceScanningOverlay::updateRimProgress)
}
}
@@ -363,13 +386,24 @@ class FaceScanningOverlay(
private const val CAMERA_PROTECTION_APPEAR_DURATION = 250L
private const val PULSE_APPEAR_DURATION = 250L // without start delay
- private const val PULSE_DURATION_INWARDS = 500L
- private const val PULSE_DURATION_OUTWARDS = 500L
+ private const val HALF_PULSE_DURATION = 500L
private const val PULSE_SUCCESS_DISAPPEAR_DURATION = 400L
private const val CAMERA_PROTECTION_SUCCESS_DISAPPEAR_DURATION = 500L // without start delay
private const val PULSE_ERROR_DISAPPEAR_DURATION = 200L
private const val CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION = 300L // without start delay
+
+ private fun scalePath(path: Path, scalingFactor: Float) {
+ val scaleMatrix = Matrix().apply {
+ val boundingRectangle = RectF()
+ path.computeBounds(boundingRectangle, true)
+ setScale(
+ scalingFactor, scalingFactor,
+ boundingRectangle.centerX(), boundingRectangle.centerY()
+ )
+ }
+ path.transform(scaleMatrix)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt b/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt
new file mode 100644
index 000000000000..4c3a7ff4e2eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt
@@ -0,0 +1,7 @@
+package com.android.systemui
+
+import com.android.systemui.dump.nano.SystemUIProtoDump
+
+interface ProtoDumpable : Dumpable {
+ fun dumpProto(systemUIProtoDump: SystemUIProtoDump, args: Array<String>)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index b5f42a164495..11d579d481c1 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -456,7 +456,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
}
}
- boolean needToUpdateProviderViews = false;
final String newUniqueId = mDisplayInfo.uniqueId;
if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
mDisplayUniqueId = newUniqueId;
@@ -474,37 +473,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
setupDecorations();
return;
}
-
- if (mScreenDecorHwcLayer != null) {
- updateHwLayerRoundedCornerDrawable();
- updateHwLayerRoundedCornerExistAndSize();
- }
- needToUpdateProviderViews = true;
- }
-
- final float newRatio = getPhysicalPixelDisplaySizeRatio();
- if (mRoundedCornerResDelegate.getPhysicalPixelDisplaySizeRatio() != newRatio) {
- mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(newRatio);
- if (mScreenDecorHwcLayer != null) {
- updateHwLayerRoundedCornerExistAndSize();
- }
- needToUpdateProviderViews = true;
- }
-
- if (needToUpdateProviderViews) {
- updateOverlayProviderViews(null);
- } else {
- updateOverlayProviderViews(new Integer[] {
- mFaceScanningViewId,
- R.id.display_cutout,
- R.id.display_cutout_left,
- R.id.display_cutout_right,
- R.id.display_cutout_bottom,
- });
- }
-
- if (mScreenDecorHwcLayer != null) {
- mScreenDecorHwcLayer.onDisplayChanged(newUniqueId);
}
}
};
@@ -1070,9 +1038,11 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
&& (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) {
mRotation = newRotation;
mDisplayMode = newMod;
+ mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
+ getPhysicalPixelDisplaySizeRatio());
if (mScreenDecorHwcLayer != null) {
mScreenDecorHwcLayer.pendingConfigChange = false;
- mScreenDecorHwcLayer.updateRotation(mRotation);
+ mScreenDecorHwcLayer.updateConfiguration(mDisplayUniqueId);
updateHwLayerRoundedCornerExistAndSize();
updateHwLayerRoundedCornerDrawable();
}
@@ -1111,7 +1081,8 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
context.getResources(), context.getDisplay().getUniqueId());
}
- private void updateOverlayProviderViews(@Nullable Integer[] filterIds) {
+ @VisibleForTesting
+ void updateOverlayProviderViews(@Nullable Integer[] filterIds) {
if (mOverlays == null) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 7bcba3cc1c46..50e03992df49 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -121,6 +121,6 @@ public class SystemUIService extends Service {
DumpHandler.PRIORITY_ARG_CRITICAL};
}
- mDumpHandler.dump(pw, massagedArgs);
+ mDumpHandler.dump(fd, pw, massagedArgs);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index d43e5d9d09cc..c015a21c7db4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -294,6 +294,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
});
mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation);
+ mUdfpsController.setUdfpsDisplayMode(new UdfpsDisplayMode(mContext, mExecution,
+ this));
mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect();
}
@@ -626,17 +628,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mUdfpsController.onAodInterrupt(screenX, screenY, major, minor);
}
- /**
- * Cancel a fingerprint scan manually. This will get rid of the white circle on the udfps
- * sensor area even if the user hasn't explicitly lifted their finger yet.
- */
- public void onCancelUdfps() {
- if (mUdfpsController == null) {
- return;
- }
- mUdfpsController.onCancelUdfps();
- }
-
private void sendResultAndCleanUp(@DismissedReason int reason,
@Nullable byte[] credentialAttestation) {
if (mReceiver == null) {
@@ -964,8 +955,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
} else {
Log.w(TAG, "onBiometricError callback but dialog is gone");
}
-
- onCancelUdfps();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index 0892612d1825..76cd3f4c4f1d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -16,12 +16,21 @@
package com.android.systemui.biometrics;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.WindowInsets.Type.ime;
+
import android.annotation.NonNull;
import android.content.Context;
+import android.graphics.Insets;
import android.os.UserHandle;
import android.text.InputType;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnApplyWindowInsetsListener;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.ImeAwareEditText;
@@ -31,18 +40,24 @@ import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import java.io.PrintWriter;
+
/**
* Pin and Password UI
*/
public class AuthCredentialPasswordView extends AuthCredentialView
- implements TextView.OnEditorActionListener {
+ implements TextView.OnEditorActionListener, OnApplyWindowInsetsListener, Dumpable {
private static final String TAG = "BiometricPrompt/AuthCredentialPasswordView";
private final InputMethodManager mImm;
private ImeAwareEditText mPasswordField;
+ private ViewGroup mAuthCredentialHeader;
+ private ViewGroup mAuthCredentialInput;
+ private int mBottomInset = 0;
public AuthCredentialPasswordView(Context context,
AttributeSet attrs) {
@@ -53,6 +68,9 @@ public class AuthCredentialPasswordView extends AuthCredentialView
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+
+ mAuthCredentialHeader = findViewById(R.id.auth_credential_header);
+ mAuthCredentialInput = findViewById(R.id.auth_credential_input);
mPasswordField = findViewById(R.id.lockPassword);
mPasswordField.setOnEditorActionListener(this);
// TODO: De-dupe the logic with AuthContainerView
@@ -66,6 +84,8 @@ public class AuthCredentialPasswordView extends AuthCredentialView
}
return true;
});
+
+ setOnApplyWindowInsetsListener(this);
}
@Override
@@ -127,4 +147,92 @@ public class AuthCredentialPasswordView extends AuthCredentialView
mPasswordField.setText("");
}
}
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mAuthCredentialInput == null || mAuthCredentialHeader == null || mSubtitleView == null
+ || mDescriptionView == null || mPasswordField == null || mErrorView == null) {
+ return;
+ }
+
+ int inputLeftBound;
+ int inputTopBound;
+ int headerRightBound = right;
+ int headerTopBounds = top;
+ final int subTitleBottom = (mSubtitleView.getVisibility() == GONE) ? mTitleView.getBottom()
+ : mSubtitleView.getBottom();
+ final int descBottom = (mDescriptionView.getVisibility() == GONE) ? subTitleBottom
+ : mDescriptionView.getBottom();
+ if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+ inputTopBound = (bottom - mAuthCredentialInput.getHeight()) / 2;
+ inputLeftBound = (right - left) / 2;
+ headerRightBound = inputLeftBound;
+ headerTopBounds -= Math.min(mIconView.getBottom(), mBottomInset);
+ } else {
+ inputTopBound =
+ descBottom + (bottom - descBottom - mAuthCredentialInput.getHeight()) / 2;
+ inputLeftBound = (right - left - mAuthCredentialInput.getWidth()) / 2;
+ }
+
+ if (mDescriptionView.getBottom() > mBottomInset) {
+ mAuthCredentialHeader.layout(left, headerTopBounds, headerRightBound, bottom);
+ }
+ mAuthCredentialInput.layout(inputLeftBound, inputTopBound, right, bottom);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ final int newWidth = MeasureSpec.getSize(widthMeasureSpec);
+ final int newHeight = MeasureSpec.getSize(heightMeasureSpec) - mBottomInset;
+
+ setMeasuredDimension(newWidth, newHeight);
+
+ final int halfWidthSpec = MeasureSpec.makeMeasureSpec(getWidth() / 2,
+ MeasureSpec.AT_MOST);
+ final int fullHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.UNSPECIFIED);
+ if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+ measureChildren(halfWidthSpec, fullHeightSpec);
+ } else {
+ measureChildren(widthMeasureSpec, fullHeightSpec);
+ }
+ }
+
+ @NonNull
+ @Override
+ public WindowInsets onApplyWindowInsets(@NonNull View v, WindowInsets insets) {
+
+ final Insets bottomInset = insets.getInsets(ime());
+ if (v instanceof AuthCredentialPasswordView && mBottomInset != bottomInset.bottom) {
+ mBottomInset = bottomInset.bottom;
+ if (mBottomInset > 0
+ && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+ mTitleView.setSingleLine(true);
+ mTitleView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
+ mTitleView.setMarqueeRepeatLimit(-1);
+ // select to enable marquee unless a screen reader is enabled
+ mTitleView.setSelected(!mAccessibilityManager.isEnabled()
+ || !mAccessibilityManager.isTouchExplorationEnabled());
+ } else {
+ mTitleView.setSingleLine(false);
+ mTitleView.setEllipsize(null);
+ // select to enable marquee unless a screen reader is enabled
+ mTitleView.setSelected(false);
+ }
+ requestLayout();
+ }
+ return insets;
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println(TAG + "State:");
+ pw.println(" mBottomInset=" + mBottomInset);
+ pw.println(" mAuthCredentialHeader size=(" + mAuthCredentialHeader.getWidth() + ","
+ + mAuthCredentialHeader.getHeight());
+ pw.println(" mAuthCredentialInput size=(" + mAuthCredentialInput.getWidth() + ","
+ + mAuthCredentialInput.getHeight());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index 11498dbc0b83..f9e44a0c1724 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -93,7 +93,9 @@ public class AuthCredentialPatternView extends AuthCredentialView {
@Override
protected void onErrorTimeoutFinish() {
super.onErrorTimeoutFinish();
- mLockPatternView.setEnabled(true);
+ // select to enable marquee unless a screen reader is enabled
+ mLockPatternView.setEnabled(!mAccessibilityManager.isEnabled()
+ || !mAccessibilityManager.isTouchExplorationEnabled());
}
public AuthCredentialPatternView(Context context, AttributeSet attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 4fa835e038ec..5958e6a436f1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -30,7 +30,6 @@ import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.PromptInfo;
import android.os.AsyncTask;
import android.os.CountDownTimer;
@@ -77,7 +76,7 @@ public abstract class AuthCredentialView extends LinearLayout {
protected final Handler mHandler;
protected final LockPatternUtils mLockPatternUtils;
- private final AccessibilityManager mAccessibilityManager;
+ protected final AccessibilityManager mAccessibilityManager;
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
@@ -86,10 +85,10 @@ public abstract class AuthCredentialView extends LinearLayout {
private boolean mShouldAnimatePanel;
private boolean mShouldAnimateContents;
- private TextView mTitleView;
- private TextView mSubtitleView;
- private TextView mDescriptionView;
- private ImageView mIconView;
+ protected TextView mTitleView;
+ protected TextView mSubtitleView;
+ protected TextView mDescriptionView;
+ protected ImageView mIconView;
protected TextView mErrorView;
protected @Utils.CredentialType int mCredentialType;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index a7648bffe503..b49d4523a765 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -123,7 +123,6 @@ public class UdfpsController implements DozeReceiver {
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@NonNull private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
- @Nullable private final UdfpsDisplayModeProvider mUdfpsDisplayMode;
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final SystemClock mSystemClock;
@NonNull private final UnlockedScreenOffAnimationController
@@ -139,6 +138,7 @@ public class UdfpsController implements DozeReceiver {
// TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
@Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
@Nullable private final AlternateUdfpsTouchProvider mAlternateTouchProvider;
+ @Nullable private UdfpsDisplayMode mUdfpsDisplayMode;
// Tracks the velocity of a touch to help filter out the touches that move too fast.
@Nullable private VelocityTracker mVelocityTracker;
@@ -319,6 +319,10 @@ public class UdfpsController implements DozeReceiver {
mAuthControllerUpdateUdfpsLocation = r;
}
+ public void setUdfpsDisplayMode(UdfpsDisplayMode udfpsDisplayMode) {
+ mUdfpsDisplayMode = udfpsDisplayMode;
+ }
+
/**
* Calculate the pointer speed given a velocity tracker and the pointer id.
* This assumes that the velocity tracker has already been passed all relevant motion events.
@@ -594,7 +598,6 @@ public class UdfpsController implements DozeReceiver {
@NonNull VibratorHelper vibrator,
@NonNull UdfpsHapticsSimulator udfpsHapticsSimulator,
@NonNull UdfpsShell udfpsShell,
- @NonNull Optional<UdfpsDisplayModeProvider> udfpsDisplayMode,
@NonNull KeyguardStateController keyguardStateController,
@NonNull DisplayManager displayManager,
@Main Handler mainHandler,
@@ -626,7 +629,6 @@ public class UdfpsController implements DozeReceiver {
mPowerManager = powerManager;
mAccessibilityManager = accessibilityManager;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
- mUdfpsDisplayMode = udfpsDisplayMode.orElse(null);
screenLifecycle.addObserver(mScreenObserver);
mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON;
mConfigurationController = configurationController;
@@ -786,7 +788,7 @@ public class UdfpsController implements DozeReceiver {
// ACTION_UP/ACTION_CANCEL, we need to be careful about not letting the screen
// accidentally remain in high brightness mode. As a mitigation, queue a call to
// cancel the fingerprint scan.
- mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelUdfps,
+ mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::cancelAodInterrupt,
AOD_INTERRUPT_TIMEOUT_MILLIS);
// using a hard-coded value for major and minor until it is available from the sensor
onFingerDown(requestId, screenX, screenY, minor, major);
@@ -813,26 +815,22 @@ public class UdfpsController implements DozeReceiver {
}
/**
- * Cancel UDFPS affordances - ability to hide the UDFPS overlay before the user explicitly
- * lifts their finger. Generally, this should be called on errors in the authentication flow.
- *
- * The sensor that triggers an AOD fingerprint interrupt (see onAodInterrupt) doesn't give
- * ACTION_UP/ACTION_CANCEL events, so and AOD interrupt scan needs to be cancelled manually.
+ * The sensor that triggers {@link #onAodInterrupt} doesn't emit ACTION_UP or ACTION_CANCEL
+ * events, which means the fingerprint gesture created by the AOD interrupt needs to be
+ * cancelled manually.
* This should be called when authentication either succeeds or fails. Failing to cancel the
* scan will leave the display in the UDFPS mode until the user lifts their finger. On optical
* sensors, this can result in illumination persisting for longer than necessary.
*/
- void onCancelUdfps() {
+ @VisibleForTesting
+ void cancelAodInterrupt() {
if (!mIsAodInterruptActive) {
return;
}
if (mOverlay != null && mOverlay.getOverlayView() != null) {
onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView());
}
- if (mCancelAodTimeoutAction != null) {
- mCancelAodTimeoutAction.run();
- mCancelAodTimeoutAction = null;
- }
+ mCancelAodTimeoutAction = null;
mIsAodInterruptActive = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 66a521c30f47..7d0109686351 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -21,13 +21,18 @@ import android.annotation.UiThread
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
+import android.os.Build
import android.os.RemoteException
+import android.provider.Settings
import android.util.Log
import android.util.RotationUtils
import android.view.LayoutInflater
@@ -38,6 +43,7 @@ import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
import androidx.annotation.LayoutRes
+import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
@@ -54,13 +60,16 @@ import com.android.systemui.util.time.SystemClock
private const val TAG = "UdfpsControllerOverlay"
+@VisibleForTesting
+const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui"
+
/**
* Keeps track of the overlay state and UI resources associated with a single FingerprintService
* request. This state can persist across configuration changes via the [show] and [hide]
* methods.
*/
@UiThread
-class UdfpsControllerOverlay(
+class UdfpsControllerOverlay @JvmOverloads constructor(
private val context: Context,
fingerprintManager: FingerprintManager,
private val inflater: LayoutInflater,
@@ -82,7 +91,8 @@ class UdfpsControllerOverlay(
@ShowReason val requestReason: Int,
private val controllerCallback: IUdfpsOverlayControllerCallback,
private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
- private val activityLaunchAnimator: ActivityLaunchAnimator
+ private val activityLaunchAnimator: ActivityLaunchAnimator,
+ private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -102,18 +112,19 @@ class UdfpsControllerOverlay(
gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or
- WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
+ WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
// Avoid announcing window title.
accessibilityTitle = " "
}
/** A helper if the [requestReason] was due to enrollment. */
- val enrollHelper: UdfpsEnrollHelper? = if (requestReason.isEnrollmentReason()) {
- UdfpsEnrollHelper(context, fingerprintManager, requestReason)
- } else {
- null
- }
+ val enrollHelper: UdfpsEnrollHelper? =
+ if (requestReason.isEnrollmentReason() && !shouldRemoveEnrollmentUi()) {
+ UdfpsEnrollHelper(context, fingerprintManager, requestReason)
+ } else {
+ null
+ }
/** If the overlay is currently showing. */
val isShowing: Boolean
@@ -129,6 +140,17 @@ class UdfpsControllerOverlay(
private var touchExplorationEnabled = false
+ private fun shouldRemoveEnrollmentUi(): Boolean {
+ if (isDebuggable) {
+ return Settings.Global.getInt(
+ context.contentResolver,
+ SETTING_REMOVE_ENROLLMENT_UI,
+ 0 /* def */
+ ) != 0
+ }
+ return false
+ }
+
/** Show the overlay or return false and do nothing if it is already showing. */
@SuppressLint("ClickableViewAccessibility")
fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean {
@@ -183,7 +205,18 @@ class UdfpsControllerOverlay(
view: UdfpsView,
controller: UdfpsController
): UdfpsAnimationViewController<*>? {
- return when (requestReason) {
+ val isEnrollment = when (requestReason) {
+ REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true
+ else -> false
+ }
+
+ val filteredRequestReason = if (isEnrollment && shouldRemoveEnrollmentUi()) {
+ REASON_AUTH_OTHER
+ } else {
+ requestReason
+ }
+
+ return when (filteredRequestReason) {
REASON_ENROLL_FIND_SENSOR,
REASON_ENROLL_ENROLLING -> {
UdfpsEnrollViewController(
@@ -198,7 +231,7 @@ class UdfpsControllerOverlay(
overlayParams.scaleFactor
)
}
- BiometricOverlayConstants.REASON_AUTH_KEYGUARD -> {
+ REASON_AUTH_KEYGUARD -> {
UdfpsKeyguardViewController(
view.addUdfpsView(R.layout.udfps_keyguard_view),
statusBarStateController,
@@ -216,7 +249,7 @@ class UdfpsControllerOverlay(
activityLaunchAnimator
)
}
- BiometricOverlayConstants.REASON_AUTH_BP -> {
+ REASON_AUTH_BP -> {
// note: empty controller, currently shows no visual affordance
UdfpsBpViewController(
view.addUdfpsView(R.layout.udfps_bp_view),
@@ -226,8 +259,8 @@ class UdfpsControllerOverlay(
dumpManager
)
}
- BiometricOverlayConstants.REASON_AUTH_OTHER,
- BiometricOverlayConstants.REASON_AUTH_SETTINGS -> {
+ REASON_AUTH_OTHER,
+ REASON_AUTH_SETTINGS -> {
UdfpsFpmOtherViewController(
view.addUdfpsView(R.layout.udfps_fpm_other_view),
statusBarStateController,
@@ -440,4 +473,4 @@ private fun Int.isEnrollmentReason() =
private fun Int.isImportantForAccessibility() =
this == REASON_ENROLL_FIND_SENSOR ||
this == REASON_ENROLL_ENROLLING ||
- this == BiometricOverlayConstants.REASON_AUTH_BP
+ this == REASON_AUTH_BP
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
new file mode 100644
index 000000000000..e9de7ccc7b4f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
@@ -0,0 +1,88 @@
+package com.android.systemui.biometrics
+
+import android.content.Context
+import android.os.RemoteException
+import android.os.Trace
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.concurrency.Execution
+import javax.inject.Inject
+
+private const val TAG = "UdfpsDisplayMode"
+
+/**
+ * UdfpsDisplayMode that encapsulates pixel-specific code, such as enabling the high-brightness mode
+ * (HBM) in a display-specific way and freezing the display's refresh rate.
+ */
+@SysUISingleton
+class UdfpsDisplayMode
+@Inject
+constructor(
+ private val context: Context,
+ private val execution: Execution,
+ private val authController: AuthController
+) : UdfpsDisplayModeProvider {
+
+ // The request is reset to null after it's processed.
+ private var currentRequest: Request? = null
+
+ override fun enable(onEnabled: Runnable?) {
+ execution.isMainThread()
+ Log.v(TAG, "enable")
+
+ if (currentRequest != null) {
+ Log.e(TAG, "enable | already requested")
+ return
+ }
+ if (authController.udfpsHbmListener == null) {
+ Log.e(TAG, "enable | mDisplayManagerCallback is null")
+ return
+ }
+
+ Trace.beginSection("UdfpsDisplayMode.enable")
+
+ // Track this request in one object.
+ val request = Request(context.displayId)
+ currentRequest = request
+
+ try {
+ // This method is a misnomer. It has nothing to do with HBM, its purpose is to set
+ // the appropriate display refresh rate.
+ authController.udfpsHbmListener!!.onHbmEnabled(request.displayId)
+ Log.v(TAG, "enable | requested optimal refresh rate for UDFPS")
+ } catch (e: RemoteException) {
+ Log.e(TAG, "enable", e)
+ }
+
+ onEnabled?.run() ?: Log.w(TAG, "enable | onEnabled is null")
+ Trace.endSection()
+ }
+
+ override fun disable(onDisabled: Runnable?) {
+ execution.isMainThread()
+ Log.v(TAG, "disable")
+
+ val request = currentRequest
+ if (request == null) {
+ Log.w(TAG, "disable | already disabled")
+ return
+ }
+
+ Trace.beginSection("UdfpsDisplayMode.disable")
+
+ try {
+ // Allow DisplayManager to unset the UDFPS refresh rate.
+ authController.udfpsHbmListener!!.onHbmDisabled(request.displayId)
+ Log.v(TAG, "disable | removed the UDFPS refresh rate request")
+ } catch (e: RemoteException) {
+ Log.e(TAG, "disable", e)
+ }
+
+ currentRequest = null
+ onDisabled?.run() ?: Log.w(TAG, "disable | onDisabled is null")
+ Trace.endSection()
+ }
+}
+
+/** Tracks a request to enable the UDFPS mode. */
+private data class Request(val displayId: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
index 96af42bfda22..d99625a9fbf2 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.bluetooth
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.BluetoothLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** Helper class for logging bluetooth events. */
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
index 9b7d49883222..8e062bd69d63 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
@@ -17,15 +17,11 @@
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;
@@ -33,7 +29,7 @@ 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.controls.util.MediaDataUtils;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.statusbar.phone.SystemUIDialog;
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
index 5b3a982ab5e2..d27708fc04d7 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
@@ -20,11 +20,11 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogMessage
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogMessage
import com.android.systemui.log.dagger.BroadcastDispatcherLog
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index d53e56f7b852..500f28004429 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -18,6 +18,7 @@ package com.android.systemui.classifier;
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
import static com.android.systemui.classifier.Classifier.GENERIC;
+import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
import static com.android.systemui.classifier.FalsingManagerProxy.FALSING_SUCCESS;
import static com.android.systemui.classifier.FalsingModule.BRIGHT_LINE_GESTURE_CLASSIFERS;
@@ -220,6 +221,11 @@ public class BrightLineFalsingManager implements FalsingManager {
return r;
}).collect(Collectors.toList());
+ // check for false tap if it is a seekbar interaction
+ if (interactionType == MEDIA_SEEKBAR) {
+ localResult[0] &= isFalseTap(LOW_PENALTY);
+ }
+
logDebug("False Gesture (type: " + interactionType + "): " + localResult[0]);
return localResult[0];
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index 3871248eccd5..858bac30880b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -44,9 +44,6 @@ public interface FalsingCollector {
void onQsDown();
/** */
- void setQsExpanded(boolean expanded);
-
- /** */
boolean shouldEnforceBouncer();
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index 28aac051c66d..0b7d6ab5acf7 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -49,10 +49,6 @@ public class FalsingCollectorFake implements FalsingCollector {
}
@Override
- public void setQsExpanded(boolean expanded) {
- }
-
- @Override
public boolean shouldEnforceBouncer() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index f5f9655ef24b..da3d293d543b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -23,6 +23,8 @@ import android.hardware.biometrics.BiometricSourceType;
import android.util.Log;
import android.view.MotionEvent;
+import androidx.annotation.VisibleForTesting;
+
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.dagger.SysUISingleton;
@@ -30,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -133,6 +136,7 @@ class FalsingCollectorImpl implements FalsingCollector {
ProximitySensor proximitySensor,
StatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
+ ShadeExpansionStateManager shadeExpansionStateManager,
BatteryController batteryController,
DockManager dockManager,
@Main DelayableExecutor mainExecutor,
@@ -157,6 +161,8 @@ class FalsingCollectorImpl implements FalsingCollector {
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
+ shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
+
mBatteryController.addCallback(mBatteryListener);
mDockManager.addListener(mDockEventListener);
}
@@ -193,8 +199,8 @@ class FalsingCollectorImpl implements FalsingCollector {
public void onQsDown() {
}
- @Override
- public void setQsExpanded(boolean expanded) {
+ @VisibleForTesting
+ void onQsExpansionChanged(Boolean expanded) {
if (expanded) {
unregisterSensors();
} else if (mSessionStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 05e3f1ce87a6..82e570438dab 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -31,9 +31,12 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.util.DeviceConfigProxy;
import javax.inject.Inject;
+import javax.inject.Provider;
/**
* ClipboardListener brings up a clipboard overlay when something is copied to the clipboard.
@@ -51,20 +54,30 @@ public class ClipboardListener implements
private final Context mContext;
private final DeviceConfigProxy mDeviceConfig;
- private final ClipboardOverlayControllerFactory mOverlayFactory;
+ private final Provider<ClipboardOverlayController> mOverlayProvider;
+ private final ClipboardOverlayControllerLegacyFactory mOverlayFactory;
private final ClipboardManager mClipboardManager;
private final UiEventLogger mUiEventLogger;
- private ClipboardOverlayController mClipboardOverlayController;
+ private final FeatureFlags mFeatureFlags;
+ private boolean mUsingNewOverlay;
+ private ClipboardOverlay mClipboardOverlay;
@Inject
public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy,
- ClipboardOverlayControllerFactory overlayFactory, ClipboardManager clipboardManager,
- UiEventLogger uiEventLogger) {
+ Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
+ ClipboardOverlayControllerLegacyFactory overlayFactory,
+ ClipboardManager clipboardManager,
+ UiEventLogger uiEventLogger,
+ FeatureFlags featureFlags) {
mContext = context;
mDeviceConfig = deviceConfigProxy;
+ mOverlayProvider = clipboardOverlayControllerProvider;
mOverlayFactory = overlayFactory;
mClipboardManager = clipboardManager;
mUiEventLogger = uiEventLogger;
+ mFeatureFlags = featureFlags;
+
+ mUsingNewOverlay = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
}
@Override
@@ -89,16 +102,22 @@ public class ClipboardListener implements
return;
}
- if (mClipboardOverlayController == null) {
- mClipboardOverlayController = mOverlayFactory.create(mContext);
+ boolean enabled = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
+ if (mClipboardOverlay == null || enabled != mUsingNewOverlay) {
+ mUsingNewOverlay = enabled;
+ if (enabled) {
+ mClipboardOverlay = mOverlayProvider.get();
+ } else {
+ mClipboardOverlay = mOverlayFactory.create(mContext);
+ }
mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource);
} else {
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
}
- mClipboardOverlayController.setClipData(clipData, clipSource);
- mClipboardOverlayController.setOnSessionCompleteListener(() -> {
+ mClipboardOverlay.setClipData(clipData, clipSource);
+ mClipboardOverlay.setOnSessionCompleteListener(() -> {
// Session is complete, free memory until it's needed again.
- mClipboardOverlayController = null;
+ mClipboardOverlay = null;
});
}
@@ -120,4 +139,10 @@ public class ClipboardListener implements
private static boolean isEmulator() {
return SystemProperties.getBoolean("ro.boot.qemu", false);
}
+
+ interface ClipboardOverlay {
+ void setClipData(ClipData clipData, String clipSource);
+
+ void setOnSessionCompleteListener(Runnable runnable);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 7e499ebdf691..9f338d1c6e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -17,7 +17,6 @@
package com.android.systemui.clipboardoverlay;
import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
@@ -37,11 +36,6 @@ import static java.util.Objects.requireNonNull;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.annotation.MainThread;
-import android.app.ICompatCameraControlCallback;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ClipData;
@@ -52,14 +46,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
import android.net.Uri;
@@ -67,57 +54,37 @@ import android.os.AsyncTask;
import android.os.Looper;
import android.provider.DeviceConfig;
import android.text.TextUtils;
-import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.MathUtils;
import android.util.Size;
-import android.util.TypedValue;
import android.view.Display;
-import android.view.DisplayCutout;
-import android.view.Gravity;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.PathInterpolator;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLinks;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.PhoneWindow;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.screenshot.DraggableConstraintLayout;
-import com.android.systemui.screenshot.FloatingWindowUtil;
-import com.android.systemui.screenshot.OverlayActionChip;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
import com.android.systemui.screenshot.TimeoutHandler;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Optional;
+
+import javax.inject.Inject;
/**
* Controls state and UI for the overlay that appears when something is added to the clipboard
*/
-public class ClipboardOverlayController {
+public class ClipboardOverlayController implements ClipboardListener.ClipboardOverlay {
private static final String TAG = "ClipboardOverlayCtrlr";
/** Constants for screenshot/copy deconflicting */
@@ -126,36 +93,22 @@ public class ClipboardOverlayController {
public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
- private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
- private static final int FONT_SEARCH_STEP_PX = 4;
private final Context mContext;
private final ClipboardLogger mClipboardLogger;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DisplayManager mDisplayManager;
- private final DisplayMetrics mDisplayMetrics;
- private final WindowManager mWindowManager;
- private final WindowManager.LayoutParams mWindowLayoutParams;
- private final PhoneWindow mWindow;
+ private final ClipboardOverlayWindow mWindow;
private final TimeoutHandler mTimeoutHandler;
- private final AccessibilityManager mAccessibilityManager;
private final TextClassifier mTextClassifier;
- private final DraggableConstraintLayout mView;
- private final View mClipboardPreview;
- private final ImageView mImagePreview;
- private final TextView mTextPreview;
- private final TextView mHiddenPreview;
- private final View mPreviewBorder;
- private final OverlayActionChip mEditChip;
- private final OverlayActionChip mShareChip;
- private final OverlayActionChip mRemoteCopyChip;
- private final View mActionContainerBackground;
- private final View mDismissButton;
- private final LinearLayout mActionContainer;
- private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+ private final ClipboardOverlayView mView;
private Runnable mOnSessionCompleteListener;
+ private Runnable mOnRemoteCopyTapped;
+ private Runnable mOnShareTapped;
+ private Runnable mOnEditTapped;
+ private Runnable mOnPreviewTapped;
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
@@ -163,14 +116,66 @@ public class ClipboardOverlayController {
private BroadcastReceiver mCloseDialogsReceiver;
private BroadcastReceiver mScreenshotReceiver;
- private boolean mBlockAttach = false;
private Animator mExitAnimator;
private Animator mEnterAnimator;
- private final int mOrientation;
- private boolean mKeyboardVisible;
+ private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks =
+ new ClipboardOverlayView.ClipboardOverlayCallbacks() {
+ @Override
+ public void onInteraction() {
+ mTimeoutHandler.resetTimeout();
+ }
+
+ @Override
+ public void onSwipeDismissInitiated(Animator animator) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+ mExitAnimator = animator;
+ }
+
+ @Override
+ public void onDismissComplete() {
+ hideImmediate();
+ }
+
+ @Override
+ public void onPreviewTapped() {
+ if (mOnPreviewTapped != null) {
+ mOnPreviewTapped.run();
+ }
+ }
+
+ @Override
+ public void onShareButtonTapped() {
+ if (mOnShareTapped != null) {
+ mOnShareTapped.run();
+ }
+ }
+
+ @Override
+ public void onEditButtonTapped() {
+ if (mOnEditTapped != null) {
+ mOnEditTapped.run();
+ }
+ }
+
+ @Override
+ public void onRemoteCopyButtonTapped() {
+ if (mOnRemoteCopyTapped != null) {
+ mOnRemoteCopyTapped.run();
+ }
+ }
+
+ @Override
+ public void onDismissButtonTapped() {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ animateOut();
+ }
+ };
- public ClipboardOverlayController(Context context,
+ @Inject
+ public ClipboardOverlayController(@OverlayWindowContext Context context,
+ ClipboardOverlayView clipboardOverlayView,
+ ClipboardOverlayWindow clipboardOverlayWindow,
BroadcastDispatcher broadcastDispatcher,
BroadcastSender broadcastSender,
TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
@@ -181,121 +186,26 @@ public class ClipboardOverlayController {
mClipboardLogger = new ClipboardLogger(uiEventLogger);
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+ mView = clipboardOverlayView;
+ mWindow = clipboardOverlayWindow;
+ mWindow.init(mView::setInsets, () -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+ hideImmediate();
+ });
+
mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
.getTextClassifier();
- mWindowManager = mContext.getSystemService(WindowManager.class);
-
- mDisplayMetrics = new DisplayMetrics();
- mContext.getDisplay().getRealMetrics(mDisplayMetrics);
-
mTimeoutHandler = timeoutHandler;
mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
- // Setup the window that we are going to use
- mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
- mWindowLayoutParams.setTitle("ClipboardOverlay");
-
- mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
- mWindow.setWindowManager(mWindowManager, null, null);
-
- setWindowFocusable(false);
-
- mView = (DraggableConstraintLayout)
- LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay, null);
- mActionContainerBackground =
- requireNonNull(mView.findViewById(R.id.actions_container_background));
- mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
- mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
- mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
- mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
- mHiddenPreview = requireNonNull(mView.findViewById(R.id.hidden_preview));
- mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
- mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
- mShareChip = requireNonNull(mView.findViewById(R.id.share_chip));
- mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
- mEditChip.setAlpha(1);
- mShareChip.setAlpha(1);
- mRemoteCopyChip.setAlpha(1);
- mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
-
- mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
- mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
- @Override
- public void onInteraction() {
- mTimeoutHandler.resetTimeout();
- }
-
- @Override
- public void onSwipeDismissInitiated(Animator animator) {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
- mExitAnimator = animator;
- }
+ mView.setCallbacks(mClipboardCallbacks);
- @Override
- public void onDismissComplete() {
- hideImmediate();
- }
- });
- mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
- int availableHeight = mTextPreview.getHeight()
- - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
- mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
- return true;
- });
-
- mDismissButton.setOnClickListener(view -> {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
- animateOut();
- });
-
- mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
- mRemoteCopyChip.setIcon(
- Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
- mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
- mOrientation = mContext.getResources().getConfiguration().orientation;
-
- attachWindow();
- withWindowAttached(() -> {
+ mWindow.withWindowAttached(() -> {
mWindow.setContentView(mView);
- WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
- mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
- updateInsets(insets);
- mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- WindowInsets insets =
- mWindowManager.getCurrentWindowMetrics().getWindowInsets();
- boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
- if (keyboardVisible != mKeyboardVisible) {
- mKeyboardVisible = keyboardVisible;
- updateInsets(insets);
- }
- }
- });
- mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
- new ViewRootImpl.ActivityConfigCallback() {
- @Override
- public void onConfigurationChanged(Configuration overrideConfig,
- int newDisplayId) {
- if (mContext.getResources().getConfiguration().orientation
- != mOrientation) {
- mClipboardLogger.logSessionComplete(
- CLIPBOARD_OVERLAY_DISMISSED_OTHER);
- hideImmediate();
- }
- }
-
- @Override
- public void requestCompatCameraControl(
- boolean showControl, boolean transformationApplied,
- ICompatCameraControlCallback callback) {
- Log.w(TAG, "unexpected requestCompatCameraControl call");
- }
- });
+ mView.setInsets(mWindow.getWindowInsets(),
+ mContext.getResources().getConfiguration().orientation);
});
mTimeoutHandler.setOnTimeoutRunnable(() -> {
@@ -336,21 +246,19 @@ public class ClipboardOverlayController {
broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
}
- void setClipData(ClipData clipData, String clipSource) {
+ @Override // ClipboardListener.ClipboardOverlay
+ public void setClipData(ClipData clipData, String clipSource) {
if (mExitAnimator != null && mExitAnimator.isRunning()) {
mExitAnimator.cancel();
}
reset();
- String accessibilityAnnouncement;
+ String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
&& clipData.getDescription().getExtras()
.getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
if (clipData == null || clipData.getItemCount() == 0) {
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
- mTextPreview);
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+ mView.showDefaultTextPreview();
} else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
ClipData.Item item = clipData.getItemAt(0);
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -360,53 +268,47 @@ public class ClipboardOverlayController {
}
}
if (isSensitive) {
- showEditableText(
- mContext.getResources().getString(R.string.clipboard_asterisks), true);
+ showEditableText(mContext.getString(R.string.clipboard_asterisks), true);
} else {
showEditableText(item.getText(), false);
}
- showShareChip(clipData);
+ mOnShareTapped = () -> shareContent(clipData);
+ mView.showShareChip();
accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
} else if (clipData.getItemAt(0).getUri() != null) {
if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
- showShareChip(clipData);
+ mOnShareTapped = () -> shareContent(clipData);
+ mView.showShareChip();
accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
- } else {
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
}
} else {
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
- mTextPreview);
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+ mView.showDefaultTextPreview();
}
+ maybeShowRemoteCopy(clipData);
+ animateIn();
+ mView.announceForAccessibility(accessibilityAnnouncement);
+ mTimeoutHandler.resetTimeout();
+ }
+
+ private void maybeShowRemoteCopy(ClipData clipData) {
Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
// Only show remote copy if it's available.
PackageManager packageManager = mContext.getPackageManager();
if (packageManager.resolveActivity(
remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) {
- mRemoteCopyChip.setContentDescription(
- mContext.getString(R.string.clipboard_send_nearby_description));
- mRemoteCopyChip.setVisibility(View.VISIBLE);
- mRemoteCopyChip.setOnClickListener((v) -> {
+ mView.setRemoteCopyVisibility(true);
+ mOnRemoteCopyTapped = () -> {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
mContext.startActivity(remoteCopyIntent);
animateOut();
- });
- mActionContainerBackground.setVisibility(View.VISIBLE);
+ };
} else {
- mRemoteCopyChip.setVisibility(View.GONE);
+ mView.setRemoteCopyVisibility(false);
}
- withWindowAttached(() -> {
- if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
- mView.post(this::animateIn);
- }
- mView.announceForAccessibility(accessibilityAnnouncement);
- });
- mTimeoutHandler.resetTimeout();
}
- void setOnSessionCompleteListener(Runnable runnable) {
+ @Override // ClipboardListener.ClipboardOverlay
+ public void setOnSessionCompleteListener(Runnable runnable) {
mOnSessionCompleteListener = runnable;
}
@@ -418,72 +320,29 @@ public class ClipboardOverlayController {
actions.addAll(classification.getActions());
}
mView.post(() -> {
- resetActionChips();
- if (actions.size() > 0) {
- mActionContainerBackground.setVisibility(View.VISIBLE);
- for (RemoteAction action : actions) {
- Intent targetIntent = action.getActionIntent().getIntent();
- ComponentName component = targetIntent.getComponent();
- if (component != null && !TextUtils.equals(source,
- component.getPackageName())) {
- OverlayActionChip chip = constructActionChip(action);
- mActionContainer.addView(chip);
- mActionChips.add(chip);
- break; // only show at most one action chip
- }
- }
- }
- });
- }
-
- private void showShareChip(ClipData clip) {
- mShareChip.setVisibility(View.VISIBLE);
- mActionContainerBackground.setVisibility(View.VISIBLE);
- mShareChip.setOnClickListener((v) -> shareContent(clip));
- }
-
- private OverlayActionChip constructActionChip(RemoteAction action) {
- OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
- R.layout.overlay_action_chip, mActionContainer, false);
- chip.setText(action.getTitle());
- chip.setContentDescription(action.getTitle());
- chip.setIcon(action.getIcon(), false);
- chip.setPendingIntent(action.getActionIntent(), () -> {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
- animateOut();
+ Optional<RemoteAction> action = actions.stream().filter(remoteAction -> {
+ ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
+ return component != null && !TextUtils.equals(source, component.getPackageName());
+ }).findFirst();
+ mView.resetActionChips();
+ action.ifPresent(remoteAction -> mView.setActionChip(remoteAction, () -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+ animateOut();
+ }));
});
- chip.setAlpha(1);
- return chip;
}
private void monitorOutsideTouches() {
InputManager inputManager = mContext.getSystemService(InputManager.class);
mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
- mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
- Looper.getMainLooper()) {
+ mInputEventReceiver = new InputEventReceiver(
+ mInputMonitor.getInputChannel(), Looper.getMainLooper()) {
@Override
public void onInputEvent(InputEvent event) {
if (event instanceof MotionEvent) {
MotionEvent motionEvent = (MotionEvent) event;
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
- Region touchRegion = new Region();
-
- final Rect tmpRect = new Rect();
- mPreviewBorder.getBoundsOnScreen(tmpRect);
- tmpRect.inset(
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
- -SWIPE_PADDING_DP));
- touchRegion.op(tmpRect, Region.Op.UNION);
- mActionContainerBackground.getBoundsOnScreen(tmpRect);
- tmpRect.inset(
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
- (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
- -SWIPE_PADDING_DP));
- touchRegion.op(tmpRect, Region.Op.UNION);
- mDismissButton.getBoundsOnScreen(tmpRect);
- touchRegion.op(tmpRect, Region.Op.UNION);
- if (!touchRegion.contains(
+ if (!mView.isInTouchRegion(
(int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
animateOut();
@@ -513,95 +372,27 @@ public class ClipboardOverlayController {
animateOut();
}
- private void showSinglePreview(View v) {
- mTextPreview.setVisibility(View.GONE);
- mImagePreview.setVisibility(View.GONE);
- mHiddenPreview.setVisibility(View.GONE);
- v.setVisibility(View.VISIBLE);
- }
-
- private void showTextPreview(CharSequence text, TextView textView) {
- showSinglePreview(textView);
- final CharSequence truncatedText = text.subSequence(0, Math.min(500, text.length()));
- textView.setText(truncatedText);
- updateTextSize(truncatedText, textView);
-
- textView.addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- if (right - left != oldRight - oldLeft) {
- updateTextSize(truncatedText, textView);
- }
- });
- mEditChip.setVisibility(View.GONE);
- }
-
- private void updateTextSize(CharSequence text, TextView textView) {
- Paint paint = new Paint(textView.getPaint());
- Resources res = textView.getResources();
- float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
- float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
- if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
- // If the text is a single word and would fit within the TextView at the min font size,
- // find the biggest font size that will fit.
- float fontSizePx = minFontSize;
- while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
- && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
- fontSizePx += FONT_SEARCH_STEP_PX;
- }
- // Need to turn off autosizing, otherwise setTextSize is a no-op.
- textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
- // It's possible to hit the max font size and not fill the width, so centering
- // horizontally looks better in this case.
- textView.setGravity(Gravity.CENTER);
- textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
- } else {
- // Otherwise just stick with autosize.
- textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
- (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
- textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
- }
- }
-
- private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
- float fontSizePx) {
- paint.setTextSize(fontSizePx);
- float size = paint.measureText(text.toString());
- float availableWidth = textView.getWidth() - textView.getPaddingLeft()
- - textView.getPaddingRight();
- return size < availableWidth;
- }
-
- private static boolean isOneWord(CharSequence text) {
- return text.toString().split("\\s+", 2).length == 1;
- }
-
private void showEditableText(CharSequence text, boolean hidden) {
- TextView textView = hidden ? mHiddenPreview : mTextPreview;
- showTextPreview(text, textView);
- View.OnClickListener listener = v -> editText();
- setAccessibilityActionToEdit(textView);
+ mView.showTextPreview(text, hidden);
+ mView.setEditAccessibilityAction(true);
+ mOnPreviewTapped = this::editText;
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
- mEditChip.setVisibility(View.VISIBLE);
- mActionContainerBackground.setVisibility(View.VISIBLE);
- mEditChip.setContentDescription(
- mContext.getString(R.string.clipboard_edit_text_description));
- mEditChip.setOnClickListener(listener);
+ mOnEditTapped = this::editText;
+ mView.showEditChip(mContext.getString(R.string.clipboard_edit_text_description));
}
- textView.setOnClickListener(listener);
}
private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
- View.OnClickListener listener = v -> editImage(uri);
+ Runnable listener = () -> editImage(uri);
ContentResolver resolver = mContext.getContentResolver();
String mimeType = resolver.getType(uri);
boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
if (isSensitive) {
- mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
- showSinglePreview(mHiddenPreview);
+ mView.showImagePreview(null);
if (isEditableImage) {
- mHiddenPreview.setOnClickListener(listener);
- setAccessibilityActionToEdit(mHiddenPreview);
+ mOnPreviewTapped = listener;
+ mView.setEditAccessibilityAction(true);
}
} else if (isEditableImage) { // if the MIMEtype is image, try to load
try {
@@ -609,44 +400,36 @@ public class ClipboardOverlayController {
// The width of the view is capped, height maintains aspect ratio, so allow it to be
// taller if needed.
Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
- showSinglePreview(mImagePreview);
- mImagePreview.setImageBitmap(thumbnail);
- mImagePreview.setOnClickListener(listener);
- setAccessibilityActionToEdit(mImagePreview);
+ mView.showImagePreview(thumbnail);
+ mView.setEditAccessibilityAction(true);
+ mOnPreviewTapped = listener;
} catch (IOException e) {
Log.e(TAG, "Thumbnail loading failed", e);
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
- mTextPreview);
+ mView.showDefaultTextPreview();
isEditableImage = false;
}
} else {
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
- mTextPreview);
+ mView.showDefaultTextPreview();
}
if (isEditableImage && DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
- mEditChip.setVisibility(View.VISIBLE);
- mActionContainerBackground.setVisibility(View.VISIBLE);
- mEditChip.setOnClickListener(listener);
- mEditChip.setContentDescription(
- mContext.getString(R.string.clipboard_edit_image_description));
+ mView.showEditChip(mContext.getString(R.string.clipboard_edit_image_description));
}
return isEditableImage;
}
- private void setAccessibilityActionToEdit(View view) {
- ViewCompat.replaceAccessibilityAction(view,
- AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
- mContext.getString(R.string.clipboard_edit), null);
- }
-
private void animateIn() {
- if (mAccessibilityManager.isEnabled()) {
- mDismissButton.setVisibility(View.VISIBLE);
+ if (mEnterAnimator != null && mEnterAnimator.isRunning()) {
+ return;
}
- mEnterAnimator = getEnterAnimation();
+ mEnterAnimator = mView.getEnterAnimation();
+ mEnterAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mTimeoutHandler.resetTimeout();
+ }
+ });
mEnterAnimator.start();
}
@@ -654,7 +437,7 @@ public class ClipboardOverlayController {
if (mExitAnimator != null && mExitAnimator.isRunning()) {
return;
}
- Animator anim = getExitAnimation();
+ Animator anim = mView.getExitAnimation();
anim.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@@ -676,122 +459,11 @@ public class ClipboardOverlayController {
anim.start();
}
- private Animator getEnterAnimation() {
- TimeInterpolator linearInterpolator = new LinearInterpolator();
- TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
- AnimatorSet enterAnim = new AnimatorSet();
-
- ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
- rootAnim.setInterpolator(linearInterpolator);
- rootAnim.setDuration(66);
- rootAnim.addUpdateListener(animation -> {
- mView.setAlpha(animation.getAnimatedFraction());
- });
-
- ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
- scaleAnim.setInterpolator(scaleInterpolator);
- scaleAnim.setDuration(333);
- scaleAnim.addUpdateListener(animation -> {
- float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
- mClipboardPreview.setScaleX(previewScale);
- mClipboardPreview.setScaleY(previewScale);
- mPreviewBorder.setScaleX(previewScale);
- mPreviewBorder.setScaleY(previewScale);
-
- float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
- mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
- mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
- float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
- float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
- mActionContainer.setScaleX(actionsScaleX);
- mActionContainer.setScaleY(actionsScaleY);
- mActionContainerBackground.setScaleX(actionsScaleX);
- mActionContainerBackground.setScaleY(actionsScaleY);
- });
-
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
- alphaAnim.setInterpolator(linearInterpolator);
- alphaAnim.setDuration(283);
- alphaAnim.addUpdateListener(animation -> {
- float alpha = animation.getAnimatedFraction();
- mClipboardPreview.setAlpha(alpha);
- mPreviewBorder.setAlpha(alpha);
- mDismissButton.setAlpha(alpha);
- mActionContainer.setAlpha(alpha);
- });
-
- mActionContainer.setAlpha(0);
- mPreviewBorder.setAlpha(0);
- mClipboardPreview.setAlpha(0);
- enterAnim.play(rootAnim).with(scaleAnim);
- enterAnim.play(alphaAnim).after(50).after(rootAnim);
-
- enterAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mView.setAlpha(1);
- mTimeoutHandler.resetTimeout();
- }
- });
- return enterAnim;
- }
-
- private Animator getExitAnimation() {
- TimeInterpolator linearInterpolator = new LinearInterpolator();
- TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
- AnimatorSet exitAnim = new AnimatorSet();
-
- ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
- rootAnim.setInterpolator(linearInterpolator);
- rootAnim.setDuration(100);
- rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction()));
-
- ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
- scaleAnim.setInterpolator(scaleInterpolator);
- scaleAnim.setDuration(250);
- scaleAnim.addUpdateListener(animation -> {
- float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
- mClipboardPreview.setScaleX(previewScale);
- mClipboardPreview.setScaleY(previewScale);
- mPreviewBorder.setScaleX(previewScale);
- mPreviewBorder.setScaleY(previewScale);
-
- float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
- mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
- mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
- float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
- float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
- mActionContainer.setScaleX(actionScaleX);
- mActionContainer.setScaleY(actionScaleY);
- mActionContainerBackground.setScaleX(actionScaleX);
- mActionContainerBackground.setScaleY(actionScaleY);
- });
-
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
- alphaAnim.setInterpolator(linearInterpolator);
- alphaAnim.setDuration(166);
- alphaAnim.addUpdateListener(animation -> {
- float alpha = 1 - animation.getAnimatedFraction();
- mClipboardPreview.setAlpha(alpha);
- mPreviewBorder.setAlpha(alpha);
- mDismissButton.setAlpha(alpha);
- mActionContainer.setAlpha(alpha);
- });
-
- exitAnim.play(alphaAnim).with(scaleAnim);
- exitAnim.play(rootAnim).after(150).after(alphaAnim);
- return exitAnim;
- }
-
- private void hideImmediate() {
+ void hideImmediate() {
// Note this may be called multiple times if multiple dismissal events happen at the same
// time.
mTimeoutHandler.cancelTimeout();
- final View decorView = mWindow.peekDecorView();
- if (decorView != null && decorView.isAttachedToWindow()) {
- mWindowManager.removeViewImmediate(decorView);
- }
+ mWindow.remove();
if (mCloseDialogsReceiver != null) {
mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver);
mCloseDialogsReceiver = null;
@@ -813,129 +485,20 @@ public class ClipboardOverlayController {
}
}
- private void resetActionChips() {
- for (OverlayActionChip chip : mActionChips) {
- mActionContainer.removeView(chip);
- }
- mActionChips.clear();
- }
-
private void reset() {
- mView.setTranslationX(0);
- mView.setAlpha(0);
- mActionContainerBackground.setVisibility(View.GONE);
- mShareChip.setVisibility(View.GONE);
- mEditChip.setVisibility(View.GONE);
- mRemoteCopyChip.setVisibility(View.GONE);
- resetActionChips();
+ mOnRemoteCopyTapped = null;
+ mOnShareTapped = null;
+ mOnEditTapped = null;
+ mOnPreviewTapped = null;
+ mView.reset();
mTimeoutHandler.cancelTimeout();
mClipboardLogger.reset();
}
- @MainThread
- private void attachWindow() {
- View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow() || mBlockAttach) {
- return;
- }
- mBlockAttach = true;
- mWindowManager.addView(decorView, mWindowLayoutParams);
- decorView.requestApplyInsets();
- mView.requestApplyInsets();
- decorView.getViewTreeObserver().addOnWindowAttachListener(
- new ViewTreeObserver.OnWindowAttachListener() {
- @Override
- public void onWindowAttached() {
- mBlockAttach = false;
- }
-
- @Override
- public void onWindowDetached() {
- }
- }
- );
- }
-
- private void withWindowAttached(Runnable action) {
- View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow()) {
- action.run();
- } else {
- decorView.getViewTreeObserver().addOnWindowAttachListener(
- new ViewTreeObserver.OnWindowAttachListener() {
- @Override
- public void onWindowAttached() {
- mBlockAttach = false;
- decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
- action.run();
- }
-
- @Override
- public void onWindowDetached() {
- }
- });
- }
- }
-
- private void updateInsets(WindowInsets insets) {
- int orientation = mContext.getResources().getConfiguration().orientation;
- FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
- if (p == null) {
- return;
- }
- DisplayCutout cutout = insets.getDisplayCutout();
- Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
- Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
- if (cutout == null) {
- p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
- } else {
- Insets waterfall = cutout.getWaterfallInsets();
- if (orientation == ORIENTATION_PORTRAIT) {
- p.setMargins(
- waterfall.left,
- Math.max(cutout.getSafeInsetTop(), waterfall.top),
- waterfall.right,
- Math.max(imeInsets.bottom,
- Math.max(cutout.getSafeInsetBottom(),
- Math.max(navBarInsets.bottom, waterfall.bottom))));
- } else {
- p.setMargins(
- waterfall.left,
- waterfall.top,
- waterfall.right,
- Math.max(imeInsets.bottom,
- Math.max(navBarInsets.bottom, waterfall.bottom)));
- }
- }
- mView.setLayoutParams(p);
- mView.requestLayout();
- }
-
private Display getDefaultDisplay() {
return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
}
- /**
- * Updates the window focusability. If the window is already showing, then it updates the
- * window immediately, otherwise the layout params will be applied when the window is next
- * shown.
- */
- private void setWindowFocusable(boolean focusable) {
- int flags = mWindowLayoutParams.flags;
- if (focusable) {
- mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- } else {
- mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- }
- if (mWindowLayoutParams.flags == flags) {
- return;
- }
- final View decorView = mWindow.peekDecorView();
- if (decorView != null && decorView.isAttachedToWindow()) {
- mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
- }
- }
-
static class ClipboardLogger {
private final UiEventLogger mUiEventLogger;
private boolean mGuarded = false;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java
new file mode 100644
index 000000000000..3a040829ba0c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java
@@ -0,0 +1,963 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EDIT_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.MainThread;
+import android.app.ICompatCameraControlCallback;
+import android.app.RemoteAction;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
+import android.hardware.input.InputManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Looper;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.Size;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.policy.PhoneWindow;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.screenshot.DraggableConstraintLayout;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+import com.android.systemui.screenshot.OverlayActionChip;
+import com.android.systemui.screenshot.TimeoutHandler;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Controls state and UI for the overlay that appears when something is added to the clipboard
+ */
+public class ClipboardOverlayControllerLegacy implements ClipboardListener.ClipboardOverlay {
+ private static final String TAG = "ClipboardOverlayCtrlr";
+ private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
+
+ /** Constants for screenshot/copy deconflicting */
+ public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT";
+ public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF";
+ public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
+
+ private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard";
+
+ private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
+ private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
+ private static final int FONT_SEARCH_STEP_PX = 4;
+
+ private final Context mContext;
+ private final ClipboardLogger mClipboardLogger;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final DisplayManager mDisplayManager;
+ private final DisplayMetrics mDisplayMetrics;
+ private final WindowManager mWindowManager;
+ private final WindowManager.LayoutParams mWindowLayoutParams;
+ private final PhoneWindow mWindow;
+ private final TimeoutHandler mTimeoutHandler;
+ private final AccessibilityManager mAccessibilityManager;
+ private final TextClassifier mTextClassifier;
+
+ private final DraggableConstraintLayout mView;
+ private final View mClipboardPreview;
+ private final ImageView mImagePreview;
+ private final TextView mTextPreview;
+ private final TextView mHiddenPreview;
+ private final View mPreviewBorder;
+ private final OverlayActionChip mEditChip;
+ private final OverlayActionChip mShareChip;
+ private final OverlayActionChip mRemoteCopyChip;
+ private final View mActionContainerBackground;
+ private final View mDismissButton;
+ private final LinearLayout mActionContainer;
+ private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+
+ private Runnable mOnSessionCompleteListener;
+
+ private InputMonitor mInputMonitor;
+ private InputEventReceiver mInputEventReceiver;
+
+ private BroadcastReceiver mCloseDialogsReceiver;
+ private BroadcastReceiver mScreenshotReceiver;
+
+ private boolean mBlockAttach = false;
+ private Animator mExitAnimator;
+ private Animator mEnterAnimator;
+ private final int mOrientation;
+ private boolean mKeyboardVisible;
+
+
+ public ClipboardOverlayControllerLegacy(Context context,
+ BroadcastDispatcher broadcastDispatcher,
+ BroadcastSender broadcastSender,
+ TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
+ mBroadcastDispatcher = broadcastDispatcher;
+ mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
+ final Context displayContext = context.createDisplayContext(getDefaultDisplay());
+ mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
+
+ mClipboardLogger = new ClipboardLogger(uiEventLogger);
+
+ mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+ mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
+ .getTextClassifier();
+
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+
+ mDisplayMetrics = new DisplayMetrics();
+ mContext.getDisplay().getRealMetrics(mDisplayMetrics);
+
+ mTimeoutHandler = timeoutHandler;
+ mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
+
+ // Setup the window that we are going to use
+ mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
+ mWindowLayoutParams.setTitle("ClipboardOverlay");
+
+ mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
+ mWindow.setWindowManager(mWindowManager, null, null);
+
+ setWindowFocusable(false);
+
+ mView = (DraggableConstraintLayout)
+ LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay_legacy, null);
+ mActionContainerBackground =
+ requireNonNull(mView.findViewById(R.id.actions_container_background));
+ mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
+ mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
+ mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
+ mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
+ mHiddenPreview = requireNonNull(mView.findViewById(R.id.hidden_preview));
+ mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
+ mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
+ mShareChip = requireNonNull(mView.findViewById(R.id.share_chip));
+ mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
+ mEditChip.setAlpha(1);
+ mShareChip.setAlpha(1);
+ mRemoteCopyChip.setAlpha(1);
+ mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
+
+ mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
+ mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
+ @Override
+ public void onInteraction() {
+ mTimeoutHandler.resetTimeout();
+ }
+
+ @Override
+ public void onSwipeDismissInitiated(Animator animator) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+ mExitAnimator = animator;
+ }
+
+ @Override
+ public void onDismissComplete() {
+ hideImmediate();
+ }
+ });
+
+ mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
+ int availableHeight = mTextPreview.getHeight()
+ - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
+ mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
+ return true;
+ });
+
+ mDismissButton.setOnClickListener(view -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ animateOut();
+ });
+
+ mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
+ mRemoteCopyChip.setIcon(
+ Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
+ mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
+ mOrientation = mContext.getResources().getConfiguration().orientation;
+
+ attachWindow();
+ withWindowAttached(() -> {
+ mWindow.setContentView(mView);
+ WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+ updateInsets(insets);
+ mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ WindowInsets insets =
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+ if (keyboardVisible != mKeyboardVisible) {
+ mKeyboardVisible = keyboardVisible;
+ updateInsets(insets);
+ }
+ }
+ });
+ mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
+ new ViewRootImpl.ActivityConfigCallback() {
+ @Override
+ public void onConfigurationChanged(Configuration overrideConfig,
+ int newDisplayId) {
+ if (mContext.getResources().getConfiguration().orientation
+ != mOrientation) {
+ mClipboardLogger.logSessionComplete(
+ CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+ hideImmediate();
+ }
+ }
+
+ @Override
+ public void requestCompatCameraControl(
+ boolean showControl, boolean transformationApplied,
+ ICompatCameraControlCallback callback) {
+ Log.w(TAG, "unexpected requestCompatCameraControl call");
+ }
+ });
+ });
+
+ mTimeoutHandler.setOnTimeoutRunnable(() -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
+ animateOut();
+ });
+
+ mCloseDialogsReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+ animateOut();
+ }
+ }
+ };
+
+ mBroadcastDispatcher.registerReceiver(mCloseDialogsReceiver,
+ new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
+ mScreenshotReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (SCREENSHOT_ACTION.equals(intent.getAction())) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+ animateOut();
+ }
+ }
+ };
+
+ mBroadcastDispatcher.registerReceiver(mScreenshotReceiver,
+ new IntentFilter(SCREENSHOT_ACTION), null, null, Context.RECEIVER_EXPORTED,
+ SELF_PERMISSION);
+ monitorOutsideTouches();
+
+ Intent copyIntent = new Intent(COPY_OVERLAY_ACTION);
+ // Set package name so the system knows it's safe
+ copyIntent.setPackage(mContext.getPackageName());
+ broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
+ }
+
+ @Override // ClipboardListener.ClipboardOverlay
+ public void setClipData(ClipData clipData, String clipSource) {
+ if (mExitAnimator != null && mExitAnimator.isRunning()) {
+ mExitAnimator.cancel();
+ }
+ reset();
+ String accessibilityAnnouncement;
+
+ boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
+ && clipData.getDescription().getExtras()
+ .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
+ if (clipData == null || clipData.getItemCount() == 0) {
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
+ accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+ } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
+ ClipData.Item item = clipData.getItemAt(0);
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
+ if (item.getTextLinks() != null) {
+ AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
+ }
+ }
+ if (isSensitive) {
+ showEditableText(
+ mContext.getResources().getString(R.string.clipboard_asterisks), true);
+ } else {
+ showEditableText(item.getText(), false);
+ }
+ showShareChip(clipData);
+ accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
+ } else if (clipData.getItemAt(0).getUri() != null) {
+ if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
+ showShareChip(clipData);
+ accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
+ } else {
+ accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+ }
+ } else {
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
+ accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+ }
+ Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
+ // Only show remote copy if it's available.
+ PackageManager packageManager = mContext.getPackageManager();
+ if (packageManager.resolveActivity(
+ remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) {
+ mRemoteCopyChip.setContentDescription(
+ mContext.getString(R.string.clipboard_send_nearby_description));
+ mRemoteCopyChip.setVisibility(View.VISIBLE);
+ mRemoteCopyChip.setOnClickListener((v) -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
+ mContext.startActivity(remoteCopyIntent);
+ animateOut();
+ });
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ } else {
+ mRemoteCopyChip.setVisibility(View.GONE);
+ }
+ withWindowAttached(() -> {
+ if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
+ mView.post(this::animateIn);
+ }
+ mView.announceForAccessibility(accessibilityAnnouncement);
+ });
+ mTimeoutHandler.resetTimeout();
+ }
+
+ @Override // ClipboardListener.ClipboardOverlay
+ public void setOnSessionCompleteListener(Runnable runnable) {
+ mOnSessionCompleteListener = runnable;
+ }
+
+ private void classifyText(ClipData.Item item, String source) {
+ ArrayList<RemoteAction> actions = new ArrayList<>();
+ for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
+ TextClassification classification = mTextClassifier.classifyText(
+ item.getText(), link.getStart(), link.getEnd(), null);
+ actions.addAll(classification.getActions());
+ }
+ mView.post(() -> {
+ resetActionChips();
+ if (actions.size() > 0) {
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ for (RemoteAction action : actions) {
+ Intent targetIntent = action.getActionIntent().getIntent();
+ ComponentName component = targetIntent.getComponent();
+ if (component != null && !TextUtils.equals(source,
+ component.getPackageName())) {
+ OverlayActionChip chip = constructActionChip(action);
+ mActionContainer.addView(chip);
+ mActionChips.add(chip);
+ break; // only show at most one action chip
+ }
+ }
+ }
+ });
+ }
+
+ private void showShareChip(ClipData clip) {
+ mShareChip.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ mShareChip.setOnClickListener((v) -> shareContent(clip));
+ }
+
+ private OverlayActionChip constructActionChip(RemoteAction action) {
+ OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
+ R.layout.overlay_action_chip, mActionContainer, false);
+ chip.setText(action.getTitle());
+ chip.setContentDescription(action.getTitle());
+ chip.setIcon(action.getIcon(), false);
+ chip.setPendingIntent(action.getActionIntent(), () -> {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+ animateOut();
+ });
+ chip.setAlpha(1);
+ return chip;
+ }
+
+ private void monitorOutsideTouches() {
+ InputManager inputManager = mContext.getSystemService(InputManager.class);
+ mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
+ mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
+ Looper.getMainLooper()) {
+ @Override
+ public void onInputEvent(InputEvent event) {
+ if (event instanceof MotionEvent) {
+ MotionEvent motionEvent = (MotionEvent) event;
+ if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ Region touchRegion = new Region();
+
+ final Rect tmpRect = new Rect();
+ mPreviewBorder.getBoundsOnScreen(tmpRect);
+ tmpRect.inset(
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
+ -SWIPE_PADDING_DP));
+ touchRegion.op(tmpRect, Region.Op.UNION);
+ mActionContainerBackground.getBoundsOnScreen(tmpRect);
+ tmpRect.inset(
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
+ -SWIPE_PADDING_DP));
+ touchRegion.op(tmpRect, Region.Op.UNION);
+ mDismissButton.getBoundsOnScreen(tmpRect);
+ touchRegion.op(tmpRect, Region.Op.UNION);
+ if (!touchRegion.contains(
+ (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
+ animateOut();
+ }
+ }
+ }
+ finishInputEvent(event, true /* handled */);
+ }
+ };
+ }
+
+ private void editImage(Uri uri) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
+ mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext));
+ animateOut();
+ }
+
+ private void editText() {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
+ mContext.startActivity(IntentCreator.getTextEditorIntent(mContext));
+ animateOut();
+ }
+
+ private void shareContent(ClipData clip) {
+ mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED);
+ mContext.startActivity(IntentCreator.getShareIntent(clip, mContext));
+ animateOut();
+ }
+
+ private void showSinglePreview(View v) {
+ mTextPreview.setVisibility(View.GONE);
+ mImagePreview.setVisibility(View.GONE);
+ mHiddenPreview.setVisibility(View.GONE);
+ v.setVisibility(View.VISIBLE);
+ }
+
+ private void showTextPreview(CharSequence text, TextView textView) {
+ showSinglePreview(textView);
+ final CharSequence truncatedText = text.subSequence(0, Math.min(500, text.length()));
+ textView.setText(truncatedText);
+ updateTextSize(truncatedText, textView);
+
+ textView.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ if (right - left != oldRight - oldLeft) {
+ updateTextSize(truncatedText, textView);
+ }
+ });
+ mEditChip.setVisibility(View.GONE);
+ }
+
+ private void updateTextSize(CharSequence text, TextView textView) {
+ Paint paint = new Paint(textView.getPaint());
+ Resources res = textView.getResources();
+ float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
+ float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
+ if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
+ // If the text is a single word and would fit within the TextView at the min font size,
+ // find the biggest font size that will fit.
+ float fontSizePx = minFontSize;
+ while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
+ && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
+ fontSizePx += FONT_SEARCH_STEP_PX;
+ }
+ // Need to turn off autosizing, otherwise setTextSize is a no-op.
+ textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
+ // It's possible to hit the max font size and not fill the width, so centering
+ // horizontally looks better in this case.
+ textView.setGravity(Gravity.CENTER);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
+ } else {
+ // Otherwise just stick with autosize.
+ textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
+ (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
+ textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
+ }
+ }
+
+ private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
+ float fontSizePx) {
+ paint.setTextSize(fontSizePx);
+ float size = paint.measureText(text.toString());
+ float availableWidth = textView.getWidth() - textView.getPaddingLeft()
+ - textView.getPaddingRight();
+ return size < availableWidth;
+ }
+
+ private static boolean isOneWord(CharSequence text) {
+ return text.toString().split("\\s+", 2).length == 1;
+ }
+
+ private void showEditableText(CharSequence text, boolean hidden) {
+ TextView textView = hidden ? mHiddenPreview : mTextPreview;
+ showTextPreview(text, textView);
+ View.OnClickListener listener = v -> editText();
+ setAccessibilityActionToEdit(textView);
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
+ mEditChip.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ mEditChip.setContentDescription(
+ mContext.getString(R.string.clipboard_edit_text_description));
+ mEditChip.setOnClickListener(listener);
+ }
+ textView.setOnClickListener(listener);
+ }
+
+ private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
+ View.OnClickListener listener = v -> editImage(uri);
+ ContentResolver resolver = mContext.getContentResolver();
+ String mimeType = resolver.getType(uri);
+ boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
+ if (isSensitive) {
+ mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
+ showSinglePreview(mHiddenPreview);
+ if (isEditableImage) {
+ mHiddenPreview.setOnClickListener(listener);
+ setAccessibilityActionToEdit(mHiddenPreview);
+ }
+ } else if (isEditableImage) { // if the MIMEtype is image, try to load
+ try {
+ int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
+ // The width of the view is capped, height maintains aspect ratio, so allow it to be
+ // taller if needed.
+ Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
+ showSinglePreview(mImagePreview);
+ mImagePreview.setImageBitmap(thumbnail);
+ mImagePreview.setOnClickListener(listener);
+ setAccessibilityActionToEdit(mImagePreview);
+ } catch (IOException e) {
+ Log.e(TAG, "Thumbnail loading failed", e);
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
+ isEditableImage = false;
+ }
+ } else {
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
+ }
+ if (isEditableImage && DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
+ mEditChip.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ mEditChip.setOnClickListener(listener);
+ mEditChip.setContentDescription(
+ mContext.getString(R.string.clipboard_edit_image_description));
+ }
+ return isEditableImage;
+ }
+
+ private void setAccessibilityActionToEdit(View view) {
+ ViewCompat.replaceAccessibilityAction(view,
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+ mContext.getString(R.string.clipboard_edit), null);
+ }
+
+ private void animateIn() {
+ if (mAccessibilityManager.isEnabled()) {
+ mDismissButton.setVisibility(View.VISIBLE);
+ }
+ mEnterAnimator = getEnterAnimation();
+ mEnterAnimator.start();
+ }
+
+ private void animateOut() {
+ if (mExitAnimator != null && mExitAnimator.isRunning()) {
+ return;
+ }
+ Animator anim = getExitAnimation();
+ anim.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (!mCancelled) {
+ hideImmediate();
+ }
+ }
+ });
+ mExitAnimator = anim;
+ anim.start();
+ }
+
+ private Animator getEnterAnimation() {
+ TimeInterpolator linearInterpolator = new LinearInterpolator();
+ TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
+ AnimatorSet enterAnim = new AnimatorSet();
+
+ ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+ rootAnim.setInterpolator(linearInterpolator);
+ rootAnim.setDuration(66);
+ rootAnim.addUpdateListener(animation -> {
+ mView.setAlpha(animation.getAnimatedFraction());
+ });
+
+ ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+ scaleAnim.setInterpolator(scaleInterpolator);
+ scaleAnim.setDuration(333);
+ scaleAnim.addUpdateListener(animation -> {
+ float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+ mClipboardPreview.setScaleX(previewScale);
+ mClipboardPreview.setScaleY(previewScale);
+ mPreviewBorder.setScaleX(previewScale);
+ mPreviewBorder.setScaleY(previewScale);
+
+ float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+ mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+ mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+ float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
+ float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+ mActionContainer.setScaleX(actionsScaleX);
+ mActionContainer.setScaleY(actionsScaleY);
+ mActionContainerBackground.setScaleX(actionsScaleX);
+ mActionContainerBackground.setScaleY(actionsScaleY);
+ });
+
+ ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+ alphaAnim.setInterpolator(linearInterpolator);
+ alphaAnim.setDuration(283);
+ alphaAnim.addUpdateListener(animation -> {
+ float alpha = animation.getAnimatedFraction();
+ mClipboardPreview.setAlpha(alpha);
+ mPreviewBorder.setAlpha(alpha);
+ mDismissButton.setAlpha(alpha);
+ mActionContainer.setAlpha(alpha);
+ });
+
+ mActionContainer.setAlpha(0);
+ mPreviewBorder.setAlpha(0);
+ mClipboardPreview.setAlpha(0);
+ enterAnim.play(rootAnim).with(scaleAnim);
+ enterAnim.play(alphaAnim).after(50).after(rootAnim);
+
+ enterAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mView.setAlpha(1);
+ mTimeoutHandler.resetTimeout();
+ }
+ });
+ return enterAnim;
+ }
+
+ private Animator getExitAnimation() {
+ TimeInterpolator linearInterpolator = new LinearInterpolator();
+ TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
+ AnimatorSet exitAnim = new AnimatorSet();
+
+ ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+ rootAnim.setInterpolator(linearInterpolator);
+ rootAnim.setDuration(100);
+ rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction()));
+
+ ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+ scaleAnim.setInterpolator(scaleInterpolator);
+ scaleAnim.setDuration(250);
+ scaleAnim.addUpdateListener(animation -> {
+ float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+ mClipboardPreview.setScaleX(previewScale);
+ mClipboardPreview.setScaleY(previewScale);
+ mPreviewBorder.setScaleX(previewScale);
+ mPreviewBorder.setScaleY(previewScale);
+
+ float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+ mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+ mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+ float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
+ float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+ mActionContainer.setScaleX(actionScaleX);
+ mActionContainer.setScaleY(actionScaleY);
+ mActionContainerBackground.setScaleX(actionScaleX);
+ mActionContainerBackground.setScaleY(actionScaleY);
+ });
+
+ ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+ alphaAnim.setInterpolator(linearInterpolator);
+ alphaAnim.setDuration(166);
+ alphaAnim.addUpdateListener(animation -> {
+ float alpha = 1 - animation.getAnimatedFraction();
+ mClipboardPreview.setAlpha(alpha);
+ mPreviewBorder.setAlpha(alpha);
+ mDismissButton.setAlpha(alpha);
+ mActionContainer.setAlpha(alpha);
+ });
+
+ exitAnim.play(alphaAnim).with(scaleAnim);
+ exitAnim.play(rootAnim).after(150).after(alphaAnim);
+ return exitAnim;
+ }
+
+ private void hideImmediate() {
+ // Note this may be called multiple times if multiple dismissal events happen at the same
+ // time.
+ mTimeoutHandler.cancelTimeout();
+ final View decorView = mWindow.peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ mWindowManager.removeViewImmediate(decorView);
+ }
+ if (mCloseDialogsReceiver != null) {
+ mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver);
+ mCloseDialogsReceiver = null;
+ }
+ if (mScreenshotReceiver != null) {
+ mBroadcastDispatcher.unregisterReceiver(mScreenshotReceiver);
+ mScreenshotReceiver = null;
+ }
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ }
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
+ if (mOnSessionCompleteListener != null) {
+ mOnSessionCompleteListener.run();
+ }
+ }
+
+ private void resetActionChips() {
+ for (OverlayActionChip chip : mActionChips) {
+ mActionContainer.removeView(chip);
+ }
+ mActionChips.clear();
+ }
+
+ private void reset() {
+ mView.setTranslationX(0);
+ mView.setAlpha(0);
+ mActionContainerBackground.setVisibility(View.GONE);
+ mShareChip.setVisibility(View.GONE);
+ mEditChip.setVisibility(View.GONE);
+ mRemoteCopyChip.setVisibility(View.GONE);
+ resetActionChips();
+ mTimeoutHandler.cancelTimeout();
+ mClipboardLogger.reset();
+ }
+
+ @MainThread
+ private void attachWindow() {
+ View decorView = mWindow.getDecorView();
+ if (decorView.isAttachedToWindow() || mBlockAttach) {
+ return;
+ }
+ mBlockAttach = true;
+ mWindowManager.addView(decorView, mWindowLayoutParams);
+ decorView.requestApplyInsets();
+ mView.requestApplyInsets();
+ decorView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ mBlockAttach = false;
+ }
+
+ @Override
+ public void onWindowDetached() {
+ }
+ }
+ );
+ }
+
+ private void withWindowAttached(Runnable action) {
+ View decorView = mWindow.getDecorView();
+ if (decorView.isAttachedToWindow()) {
+ action.run();
+ } else {
+ decorView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ mBlockAttach = false;
+ decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+ action.run();
+ }
+
+ @Override
+ public void onWindowDetached() {
+ }
+ });
+ }
+ }
+
+ private void updateInsets(WindowInsets insets) {
+ int orientation = mContext.getResources().getConfiguration().orientation;
+ FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
+ if (p == null) {
+ return;
+ }
+ DisplayCutout cutout = insets.getDisplayCutout();
+ Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
+ Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
+ if (cutout == null) {
+ p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
+ } else {
+ Insets waterfall = cutout.getWaterfallInsets();
+ if (orientation == ORIENTATION_PORTRAIT) {
+ p.setMargins(
+ waterfall.left,
+ Math.max(cutout.getSafeInsetTop(), waterfall.top),
+ waterfall.right,
+ Math.max(imeInsets.bottom,
+ Math.max(cutout.getSafeInsetBottom(),
+ Math.max(navBarInsets.bottom, waterfall.bottom))));
+ } else {
+ p.setMargins(
+ waterfall.left,
+ waterfall.top,
+ waterfall.right,
+ Math.max(imeInsets.bottom,
+ Math.max(navBarInsets.bottom, waterfall.bottom)));
+ }
+ }
+ mView.setLayoutParams(p);
+ mView.requestLayout();
+ }
+
+ private Display getDefaultDisplay() {
+ return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Updates the window focusability. If the window is already showing, then it updates the
+ * window immediately, otherwise the layout params will be applied when the window is next
+ * shown.
+ */
+ private void setWindowFocusable(boolean focusable) {
+ int flags = mWindowLayoutParams.flags;
+ if (focusable) {
+ mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ } else {
+ mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ }
+ if (mWindowLayoutParams.flags == flags) {
+ return;
+ }
+ final View decorView = mWindow.peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
+ }
+ }
+
+ static class ClipboardLogger {
+ private final UiEventLogger mUiEventLogger;
+ private boolean mGuarded = false;
+
+ ClipboardLogger(UiEventLogger uiEventLogger) {
+ mUiEventLogger = uiEventLogger;
+ }
+
+ void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) {
+ if (!mGuarded) {
+ mGuarded = true;
+ mUiEventLogger.log(event);
+ }
+ }
+
+ void reset() {
+ mGuarded = false;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
index 8b0b2a59dd92..0d989a78947d 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
@@ -27,17 +27,17 @@ import com.android.systemui.screenshot.TimeoutHandler;
import javax.inject.Inject;
/**
- * A factory that churns out ClipboardOverlayControllers on demand.
+ * A factory that churns out ClipboardOverlayControllerLegacys on demand.
*/
@SysUISingleton
-public class ClipboardOverlayControllerFactory {
+public class ClipboardOverlayControllerLegacyFactory {
private final UiEventLogger mUiEventLogger;
private final BroadcastDispatcher mBroadcastDispatcher;
private final BroadcastSender mBroadcastSender;
@Inject
- public ClipboardOverlayControllerFactory(BroadcastDispatcher broadcastDispatcher,
+ public ClipboardOverlayControllerLegacyFactory(BroadcastDispatcher broadcastDispatcher,
BroadcastSender broadcastSender, UiEventLogger uiEventLogger) {
this.mBroadcastDispatcher = broadcastDispatcher;
this.mBroadcastSender = broadcastSender;
@@ -45,10 +45,10 @@ public class ClipboardOverlayControllerFactory {
}
/**
- * One new ClipboardOverlayController, coming right up!
+ * One new ClipboardOverlayControllerLegacy, coming right up!
*/
- public ClipboardOverlayController create(Context context) {
- return new ClipboardOverlayController(context, mBroadcastDispatcher, mBroadcastSender,
+ public ClipboardOverlayControllerLegacy create(Context context) {
+ return new ClipboardOverlayControllerLegacy(context, mBroadcastDispatcher, mBroadcastSender,
new TimeoutHandler(context), mUiEventLogger);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
new file mode 100644
index 000000000000..2d3315759371
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.MathUtils;
+import android.util.TypedValue;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
+import com.android.systemui.R;
+import com.android.systemui.screenshot.DraggableConstraintLayout;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+import com.android.systemui.screenshot.OverlayActionChip;
+
+import java.util.ArrayList;
+
+/**
+ * Handles the visual elements and animations for the clipboard overlay.
+ */
+public class ClipboardOverlayView extends DraggableConstraintLayout {
+
+ interface ClipboardOverlayCallbacks extends SwipeDismissCallbacks {
+ void onDismissButtonTapped();
+
+ void onRemoteCopyButtonTapped();
+
+ void onEditButtonTapped();
+
+ void onShareButtonTapped();
+
+ void onPreviewTapped();
+ }
+
+ private static final String TAG = "ClipboardView";
+
+ private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
+ private static final int FONT_SEARCH_STEP_PX = 4;
+
+ private final DisplayMetrics mDisplayMetrics;
+ private final AccessibilityManager mAccessibilityManager;
+ private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+
+ private View mClipboardPreview;
+ private ImageView mImagePreview;
+ private TextView mTextPreview;
+ private TextView mHiddenPreview;
+ private View mPreviewBorder;
+ private OverlayActionChip mEditChip;
+ private OverlayActionChip mShareChip;
+ private OverlayActionChip mRemoteCopyChip;
+ private View mActionContainerBackground;
+ private View mDismissButton;
+ private LinearLayout mActionContainer;
+
+ public ClipboardOverlayView(Context context) {
+ this(context, null);
+ }
+
+ public ClipboardOverlayView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ClipboardOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mDisplayMetrics = new DisplayMetrics();
+ mContext.getDisplay().getRealMetrics(mDisplayMetrics);
+ mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mActionContainerBackground =
+ requireNonNull(findViewById(R.id.actions_container_background));
+ mActionContainer = requireNonNull(findViewById(R.id.actions));
+ mClipboardPreview = requireNonNull(findViewById(R.id.clipboard_preview));
+ mImagePreview = requireNonNull(findViewById(R.id.image_preview));
+ mTextPreview = requireNonNull(findViewById(R.id.text_preview));
+ mHiddenPreview = requireNonNull(findViewById(R.id.hidden_preview));
+ mPreviewBorder = requireNonNull(findViewById(R.id.preview_border));
+ mEditChip = requireNonNull(findViewById(R.id.edit_chip));
+ mShareChip = requireNonNull(findViewById(R.id.share_chip));
+ mRemoteCopyChip = requireNonNull(findViewById(R.id.remote_copy_chip));
+ mDismissButton = requireNonNull(findViewById(R.id.dismiss_button));
+
+ mEditChip.setAlpha(1);
+ mShareChip.setAlpha(1);
+ mRemoteCopyChip.setAlpha(1);
+ mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
+
+ mEditChip.setIcon(
+ Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
+ mRemoteCopyChip.setIcon(
+ Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
+ mShareChip.setIcon(
+ Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
+
+ mRemoteCopyChip.setContentDescription(
+ mContext.getString(R.string.clipboard_send_nearby_description));
+
+ mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
+ int availableHeight = mTextPreview.getHeight()
+ - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
+ mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
+ return true;
+ });
+ super.onFinishInflate();
+ }
+
+ @Override
+ public void setCallbacks(SwipeDismissCallbacks callbacks) {
+ super.setCallbacks(callbacks);
+ ClipboardOverlayCallbacks clipboardCallbacks = (ClipboardOverlayCallbacks) callbacks;
+ mEditChip.setOnClickListener(v -> clipboardCallbacks.onEditButtonTapped());
+ mShareChip.setOnClickListener(v -> clipboardCallbacks.onShareButtonTapped());
+ mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
+ mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
+ mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
+ }
+
+ void setEditAccessibilityAction(boolean editable) {
+ if (editable) {
+ ViewCompat.replaceAccessibilityAction(mClipboardPreview,
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+ mContext.getString(R.string.clipboard_edit), null);
+ } else {
+ ViewCompat.replaceAccessibilityAction(mClipboardPreview,
+ AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+ null, null);
+ }
+ }
+
+ void setInsets(WindowInsets insets, int orientation) {
+ FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) getLayoutParams();
+ if (p == null) {
+ return;
+ }
+ Rect margins = computeMargins(insets, orientation);
+ p.setMargins(margins.left, margins.top, margins.right, margins.bottom);
+ setLayoutParams(p);
+ requestLayout();
+ }
+
+ boolean isInTouchRegion(int x, int y) {
+ Region touchRegion = new Region();
+ final Rect tmpRect = new Rect();
+
+ mPreviewBorder.getBoundsOnScreen(tmpRect);
+ tmpRect.inset(
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+ touchRegion.op(tmpRect, Region.Op.UNION);
+
+ mActionContainerBackground.getBoundsOnScreen(tmpRect);
+ tmpRect.inset(
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+ (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+ touchRegion.op(tmpRect, Region.Op.UNION);
+
+ mDismissButton.getBoundsOnScreen(tmpRect);
+ touchRegion.op(tmpRect, Region.Op.UNION);
+
+ return touchRegion.contains(x, y);
+ }
+
+ void setRemoteCopyVisibility(boolean visible) {
+ if (visible) {
+ mRemoteCopyChip.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ } else {
+ mRemoteCopyChip.setVisibility(View.GONE);
+ }
+ }
+
+ void showDefaultTextPreview() {
+ String copied = mContext.getString(R.string.clipboard_overlay_text_copied);
+ showTextPreview(copied, false);
+ }
+
+ void showTextPreview(CharSequence text, boolean hidden) {
+ TextView textView = hidden ? mHiddenPreview : mTextPreview;
+ showSinglePreview(textView);
+ textView.setText(text.subSequence(0, Math.min(500, text.length())));
+ updateTextSize(text, textView);
+ textView.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ if (right - left != oldRight - oldLeft) {
+ updateTextSize(text, textView);
+ }
+ });
+ mEditChip.setVisibility(View.GONE);
+ }
+
+ void showImagePreview(@Nullable Bitmap thumbnail) {
+ if (thumbnail == null) {
+ mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
+ showSinglePreview(mHiddenPreview);
+ } else {
+ mImagePreview.setImageBitmap(thumbnail);
+ showSinglePreview(mImagePreview);
+ }
+ }
+
+ void showEditChip(String contentDescription) {
+ mEditChip.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ mEditChip.setContentDescription(contentDescription);
+ }
+
+ void showShareChip() {
+ mShareChip.setVisibility(View.VISIBLE);
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ }
+
+ void reset() {
+ setTranslationX(0);
+ setAlpha(0);
+ mActionContainerBackground.setVisibility(View.GONE);
+ mDismissButton.setVisibility(View.GONE);
+ mShareChip.setVisibility(View.GONE);
+ mEditChip.setVisibility(View.GONE);
+ mRemoteCopyChip.setVisibility(View.GONE);
+ setEditAccessibilityAction(false);
+ resetActionChips();
+ }
+
+ void resetActionChips() {
+ for (OverlayActionChip chip : mActionChips) {
+ mActionContainer.removeView(chip);
+ }
+ mActionChips.clear();
+ }
+
+ Animator getEnterAnimation() {
+ if (mAccessibilityManager.isEnabled()) {
+ mDismissButton.setVisibility(View.VISIBLE);
+ }
+ TimeInterpolator linearInterpolator = new LinearInterpolator();
+ TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
+ AnimatorSet enterAnim = new AnimatorSet();
+
+ ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+ rootAnim.setInterpolator(linearInterpolator);
+ rootAnim.setDuration(66);
+ rootAnim.addUpdateListener(animation -> {
+ setAlpha(animation.getAnimatedFraction());
+ });
+
+ ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+ scaleAnim.setInterpolator(scaleInterpolator);
+ scaleAnim.setDuration(333);
+ scaleAnim.addUpdateListener(animation -> {
+ float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+ mClipboardPreview.setScaleX(previewScale);
+ mClipboardPreview.setScaleY(previewScale);
+ mPreviewBorder.setScaleX(previewScale);
+ mPreviewBorder.setScaleY(previewScale);
+
+ float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+ mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+ mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+ float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
+ float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+ mActionContainer.setScaleX(actionsScaleX);
+ mActionContainer.setScaleY(actionsScaleY);
+ mActionContainerBackground.setScaleX(actionsScaleX);
+ mActionContainerBackground.setScaleY(actionsScaleY);
+ });
+
+ ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+ alphaAnim.setInterpolator(linearInterpolator);
+ alphaAnim.setDuration(283);
+ alphaAnim.addUpdateListener(animation -> {
+ float alpha = animation.getAnimatedFraction();
+ mClipboardPreview.setAlpha(alpha);
+ mPreviewBorder.setAlpha(alpha);
+ mDismissButton.setAlpha(alpha);
+ mActionContainer.setAlpha(alpha);
+ });
+
+ mActionContainer.setAlpha(0);
+ mPreviewBorder.setAlpha(0);
+ mClipboardPreview.setAlpha(0);
+ enterAnim.play(rootAnim).with(scaleAnim);
+ enterAnim.play(alphaAnim).after(50).after(rootAnim);
+
+ enterAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ setAlpha(1);
+ }
+ });
+ return enterAnim;
+ }
+
+ Animator getExitAnimation() {
+ TimeInterpolator linearInterpolator = new LinearInterpolator();
+ TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
+ AnimatorSet exitAnim = new AnimatorSet();
+
+ ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+ rootAnim.setInterpolator(linearInterpolator);
+ rootAnim.setDuration(100);
+ rootAnim.addUpdateListener(anim -> setAlpha(1 - anim.getAnimatedFraction()));
+
+ ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+ scaleAnim.setInterpolator(scaleInterpolator);
+ scaleAnim.setDuration(250);
+ scaleAnim.addUpdateListener(animation -> {
+ float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+ mClipboardPreview.setScaleX(previewScale);
+ mClipboardPreview.setScaleY(previewScale);
+ mPreviewBorder.setScaleX(previewScale);
+ mPreviewBorder.setScaleY(previewScale);
+
+ float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+ mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+ mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+ float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
+ float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+ mActionContainer.setScaleX(actionScaleX);
+ mActionContainer.setScaleY(actionScaleY);
+ mActionContainerBackground.setScaleX(actionScaleX);
+ mActionContainerBackground.setScaleY(actionScaleY);
+ });
+
+ ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+ alphaAnim.setInterpolator(linearInterpolator);
+ alphaAnim.setDuration(166);
+ alphaAnim.addUpdateListener(animation -> {
+ float alpha = 1 - animation.getAnimatedFraction();
+ mClipboardPreview.setAlpha(alpha);
+ mPreviewBorder.setAlpha(alpha);
+ mDismissButton.setAlpha(alpha);
+ mActionContainer.setAlpha(alpha);
+ });
+
+ exitAnim.play(alphaAnim).with(scaleAnim);
+ exitAnim.play(rootAnim).after(150).after(alphaAnim);
+ return exitAnim;
+ }
+
+ void setActionChip(RemoteAction action, Runnable onFinish) {
+ mActionContainerBackground.setVisibility(View.VISIBLE);
+ OverlayActionChip chip = constructActionChip(action, onFinish);
+ mActionContainer.addView(chip);
+ mActionChips.add(chip);
+ }
+
+ private void showSinglePreview(View v) {
+ mTextPreview.setVisibility(View.GONE);
+ mImagePreview.setVisibility(View.GONE);
+ mHiddenPreview.setVisibility(View.GONE);
+ v.setVisibility(View.VISIBLE);
+ }
+
+ private OverlayActionChip constructActionChip(RemoteAction action, Runnable onFinish) {
+ OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
+ R.layout.overlay_action_chip, mActionContainer, false);
+ chip.setText(action.getTitle());
+ chip.setContentDescription(action.getTitle());
+ chip.setIcon(action.getIcon(), false);
+ chip.setPendingIntent(action.getActionIntent(), onFinish);
+ chip.setAlpha(1);
+ return chip;
+ }
+
+ private static void updateTextSize(CharSequence text, TextView textView) {
+ Paint paint = new Paint(textView.getPaint());
+ Resources res = textView.getResources();
+ float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
+ float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
+ if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
+ // If the text is a single word and would fit within the TextView at the min font size,
+ // find the biggest font size that will fit.
+ float fontSizePx = minFontSize;
+ while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
+ && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
+ fontSizePx += FONT_SEARCH_STEP_PX;
+ }
+ // Need to turn off autosizing, otherwise setTextSize is a no-op.
+ textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
+ // It's possible to hit the max font size and not fill the width, so centering
+ // horizontally looks better in this case.
+ textView.setGravity(Gravity.CENTER);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
+ } else {
+ // Otherwise just stick with autosize.
+ textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
+ (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
+ textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
+ }
+ }
+
+ private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
+ float fontSizePx) {
+ paint.setTextSize(fontSizePx);
+ float size = paint.measureText(text.toString());
+ float availableWidth = textView.getWidth() - textView.getPaddingLeft()
+ - textView.getPaddingRight();
+ return size < availableWidth;
+ }
+
+ private static boolean isOneWord(CharSequence text) {
+ return text.toString().split("\\s+", 2).length == 1;
+ }
+
+ private static Rect computeMargins(WindowInsets insets, int orientation) {
+ DisplayCutout cutout = insets.getDisplayCutout();
+ Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
+ Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
+ if (cutout == null) {
+ return new Rect(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
+ } else {
+ Insets waterfall = cutout.getWaterfallInsets();
+ if (orientation == ORIENTATION_PORTRAIT) {
+ return new Rect(
+ waterfall.left,
+ Math.max(cutout.getSafeInsetTop(), waterfall.top),
+ waterfall.right,
+ Math.max(imeInsets.bottom,
+ Math.max(cutout.getSafeInsetBottom(),
+ Math.max(navBarInsets.bottom, waterfall.bottom))));
+ } else {
+ return new Rect(
+ waterfall.left,
+ waterfall.top,
+ waterfall.right,
+ Math.max(imeInsets.bottom,
+ Math.max(navBarInsets.bottom, waterfall.bottom)));
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
new file mode 100644
index 000000000000..9dac9b393d60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.app.ICompatCameraControlCallback;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import com.android.internal.policy.PhoneWindow;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+
+import java.util.function.BiConsumer;
+
+import javax.inject.Inject;
+
+/**
+ * Handles attaching the window and the window insets for the clipboard overlay.
+ */
+public class ClipboardOverlayWindow extends PhoneWindow
+ implements ViewRootImpl.ActivityConfigCallback {
+ private static final String TAG = "ClipboardOverlayWindow";
+
+ private final Context mContext;
+ private final WindowManager mWindowManager;
+ private final WindowManager.LayoutParams mWindowLayoutParams;
+
+ private boolean mKeyboardVisible;
+ private final int mOrientation;
+ private BiConsumer<WindowInsets, Integer> mOnKeyboardChangeListener;
+ private Runnable mOnOrientationChangeListener;
+
+ @Inject
+ ClipboardOverlayWindow(@OverlayWindowContext Context context) {
+ super(context);
+ mContext = context;
+ mOrientation = mContext.getResources().getConfiguration().orientation;
+
+ // Setup the window that we are going to use
+ requestFeature(Window.FEATURE_NO_TITLE);
+ requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
+ setBackgroundDrawableResource(android.R.color.transparent);
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+ mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
+ mWindowLayoutParams.setTitle("ClipboardOverlay");
+ setWindowManager(mWindowManager, null, null);
+ setWindowFocusable(false);
+ }
+
+ /**
+ * Set callbacks for keyboard state change and orientation change and attach the window
+ *
+ * @param onKeyboardChangeListener callback for IME visibility changes
+ * @param onOrientationChangeListener callback for device orientation changes
+ */
+ public void init(@NonNull BiConsumer<WindowInsets, Integer> onKeyboardChangeListener,
+ @NonNull Runnable onOrientationChangeListener) {
+ mOnKeyboardChangeListener = onKeyboardChangeListener;
+ mOnOrientationChangeListener = onOrientationChangeListener;
+
+ attach();
+ withWindowAttached(() -> {
+ WindowInsets currentInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ mKeyboardVisible = currentInsets.isVisible(WindowInsets.Type.ime());
+ peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+ if (keyboardVisible != mKeyboardVisible) {
+ mKeyboardVisible = keyboardVisible;
+ mOnKeyboardChangeListener.accept(insets, mOrientation);
+ }
+ });
+ peekDecorView().getViewRootImpl().setActivityConfigCallback(this);
+ });
+ }
+
+ @Override // ViewRootImpl.ActivityConfigCallback
+ public void onConfigurationChanged(Configuration overrideConfig, int newDisplayId) {
+ if (mContext.getResources().getConfiguration().orientation != mOrientation) {
+ mOnOrientationChangeListener.run();
+ }
+ }
+
+ @Override // ViewRootImpl.ActivityConfigCallback
+ public void requestCompatCameraControl(boolean showControl, boolean transformationApplied,
+ ICompatCameraControlCallback callback) {
+ Log.w(TAG, "unexpected requestCompatCameraControl call");
+ }
+
+ void remove() {
+ final View decorView = peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ mWindowManager.removeViewImmediate(decorView);
+ }
+ }
+
+ WindowInsets getWindowInsets() {
+ return mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ }
+
+ void withWindowAttached(Runnable action) {
+ View decorView = getDecorView();
+ if (decorView.isAttachedToWindow()) {
+ action.run();
+ } else {
+ decorView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+ action.run();
+ }
+
+ @Override
+ public void onWindowDetached() {
+ }
+ });
+ }
+ }
+
+ @MainThread
+ private void attach() {
+ View decorView = getDecorView();
+ if (decorView.isAttachedToWindow()) {
+ return;
+ }
+ mWindowManager.addView(decorView, mWindowLayoutParams);
+ decorView.requestApplyInsets();
+ }
+
+ /**
+ * Updates the window focusability. If the window is already showing, then it updates the
+ * window immediately, otherwise the layout params will be applied when the window is next
+ * shown.
+ */
+ private void setWindowFocusable(boolean focusable) {
+ int flags = mWindowLayoutParams.flags;
+ if (focusable) {
+ mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ } else {
+ mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ }
+ if (mWindowLayoutParams.flags == flags) {
+ return;
+ }
+ final View decorView = peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
new file mode 100644
index 000000000000..22448130f7e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay.dagger;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+import android.view.LayoutInflater;
+
+import com.android.systemui.R;
+import com.android.systemui.clipboardoverlay.ClipboardOverlayView;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Module for {@link com.android.systemui.clipboardoverlay}. */
+@Module
+public interface ClipboardOverlayModule {
+
+ /**
+ *
+ */
+ @Provides
+ @OverlayWindowContext
+ static Context provideWindowContext(DisplayManager displayManager, Context context) {
+ Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+ return context.createWindowContext(display, TYPE_SCREENSHOT, null);
+ }
+
+ /**
+ *
+ */
+ @Provides
+ static ClipboardOverlayView provideClipboardOverlayView(@OverlayWindowContext Context context) {
+ return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
+ R.layout.clipboard_overlay, null);
+ }
+
+ @Qualifier
+ @Documented
+ @Retention(RUNTIME)
+ @interface OverlayWindowContext {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
index bebade0cc484..08e8293cbe9c 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
@@ -17,6 +17,7 @@
package com.android.systemui.common.shared.model
import android.annotation.StringRes
+import android.content.Context
/**
* Models a content description, that can either be already [loaded][ContentDescription.Loaded] or
@@ -30,4 +31,20 @@ sealed class ContentDescription {
data class Resource(
@StringRes val res: Int,
) : ContentDescription()
+
+ companion object {
+ /**
+ * Returns the loaded content description string, or null if we don't have one.
+ *
+ * Prefer [com.android.systemui.common.ui.binder.ContentDescriptionViewBinder.bind] over
+ * this method. This should only be used for testing or concatenation purposes.
+ */
+ fun ContentDescription?.loadContentDescription(context: Context): String? {
+ return when (this) {
+ null -> null
+ is Loaded -> this.description
+ is Resource -> context.getString(this.res)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt
index 5d0e08ffc307..4a5693202dba 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Text.kt
@@ -18,6 +18,7 @@
package com.android.systemui.common.shared.model
import android.annotation.StringRes
+import android.content.Context
/**
* Models a text, that can either be already [loaded][Text.Loaded] or be a [reference]
@@ -31,4 +32,20 @@ sealed class Text {
data class Resource(
@StringRes val res: Int,
) : Text()
+
+ companion object {
+ /**
+ * Returns the loaded test string, or null if we don't have one.
+ *
+ * Prefer [com.android.systemui.common.ui.binder.TextViewBinder.bind] over this method. This
+ * should only be used for testing or concatenation purposes.
+ */
+ fun Text?.loadText(context: Context): String? {
+ return when (this) {
+ null -> null
+ is Loaded -> this.text
+ is Resource -> context.getString(this.res)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 77b65233c112..d3b5d0edd222 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -21,6 +21,8 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
+import android.os.RemoteException
+import android.service.dreams.IDreamManager
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
@@ -40,11 +42,13 @@ import javax.inject.Inject
*/
class ControlsActivity @Inject constructor(
private val uiController: ControlsUiController,
- private val broadcastDispatcher: BroadcastDispatcher
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val dreamManager: IDreamManager,
) : ComponentActivity() {
private lateinit var parent: ViewGroup
private lateinit var broadcastReceiver: BroadcastReceiver
+ private var mExitToDream: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -81,17 +85,36 @@ class ControlsActivity @Inject constructor(
parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
parent.alpha = 0f
- uiController.show(parent, { finish() }, this)
+ uiController.show(parent, { finishOrReturnToDream() }, this)
ControlsAnimations.enterAnimation(parent).start()
}
- override fun onBackPressed() {
+ override fun onResume() {
+ super.onResume()
+ mExitToDream = intent.getBooleanExtra(ControlsUiController.EXIT_TO_DREAM, false)
+ }
+
+ fun finishOrReturnToDream() {
+ if (mExitToDream) {
+ try {
+ mExitToDream = false
+ dreamManager.dream()
+ return
+ } catch (e: RemoteException) {
+ // Fall through
+ }
+ }
finish()
}
+ override fun onBackPressed() {
+ finishOrReturnToDream()
+ }
+
override fun onStop() {
super.onStop()
+ mExitToDream = false
uiController.hide()
}
@@ -106,7 +129,8 @@ class ControlsActivity @Inject constructor(
broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.getAction()
- if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ if (action == Intent.ACTION_SCREEN_OFF ||
+ action == Intent.ACTION_DREAMING_STARTED) {
finish()
}
}
@@ -114,6 +138,7 @@ class ControlsActivity @Inject constructor(
val filter = IntentFilter()
filter.addAction(Intent.ACTION_SCREEN_OFF)
+ filter.addAction(Intent.ACTION_DREAMING_STARTED)
broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 822f8f2e6191..c1cfbcb0c211 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -27,6 +27,7 @@ interface ControlsUiController {
companion object {
public const val TAG = "ControlsUiController"
public const val EXTRA_ANIMATE = "extra_animate"
+ public const val EXIT_TO_DREAM = "extra_exit_to_dream"
}
fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 55eda0a0cb8a..721c0ba4f865 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -34,13 +34,14 @@ import com.android.systemui.log.SessionTracker
import com.android.systemui.media.RingtonePlayer
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.media.taptotransfer.sender.MediaTttSenderCoordinator
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
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
import com.android.systemui.toast.ToastUI
import com.android.systemui.usb.StorageNotification
@@ -217,6 +218,12 @@ abstract class SystemUICoreStartableModule {
@ClassKey(KeyguardLiftController::class)
abstract fun bindKeyguardLiftController(sysui: KeyguardLiftController): CoreStartable
+ /** Inject into MediaTttSenderCoordinator. */
+ @Binds
+ @IntoMap
+ @ClassKey(MediaTttSenderCoordinator::class)
+ abstract fun bindMediaTttSenderCoordinator(sysui: MediaTttSenderCoordinator): CoreStartable
+
/** Inject into MediaTttChipControllerReceiver. */
@Binds
@IntoMap
@@ -225,17 +232,15 @@ abstract class SystemUICoreStartableModule {
sysui: MediaTttChipControllerReceiver
): CoreStartable
- /** Inject into MediaTttChipControllerSender. */
- @Binds
- @IntoMap
- @ClassKey(MediaTttChipControllerSender::class)
- abstract fun bindMediaTttChipControllerSender(
- sysui: MediaTttChipControllerSender
- ): CoreStartable
-
/** Inject into MediaTttCommandLineHelper. */
@Binds
@IntoMap
@ClassKey(MediaTttCommandLineHelper::class)
abstract fun bindMediaTttCommandLineHelper(sysui: MediaTttCommandLineHelper): CoreStartable
+
+ /** Inject into ChipbarCoordinator. */
+ @Binds
+ @IntoMap
+ @ClassKey(ChipbarCoordinator::class)
+ abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index dc3dadb32669..7e31626983e7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -23,7 +23,8 @@ import android.service.dreams.IDreamManager;
import androidx.annotation.Nullable;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.clock.ClockModule;
+import com.android.keyguard.clock.ClockInfoModule;
+import com.android.keyguard.dagger.ClockRegistryModule;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
@@ -33,6 +34,7 @@ import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
import com.android.systemui.biometrics.dagger.BiometricsModule;
import com.android.systemui.classifier.FalsingModule;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.dagger.DemoModeModule;
@@ -118,7 +120,9 @@ import dagger.Provides;
AssistModule.class,
BiometricsModule.class,
BouncerViewModule.class,
- ClockModule.class,
+ ClipboardOverlayModule.class,
+ ClockInfoModule.class,
+ ClockRegistryModule.class,
CoroutinesModule.class,
DreamModule.class,
ControlsModule.class,
@@ -165,12 +169,16 @@ public abstract class SystemUIModule {
@Binds
abstract BootCompleteCache bindBootCompleteCache(BootCompleteCacheImpl bootCompleteCache);
- /** */
+ /**
+ *
+ */
@Binds
public abstract ContextComponentHelper bindComponentHelper(
ContextComponentResolver componentHelper);
- /** */
+ /**
+ *
+ */
@Binds
public abstract NotificationRowBinder bindNotificationRowBinder(
NotificationRowBinderImpl notificationRowBinder);
@@ -209,6 +217,7 @@ public abstract class SystemUIModule {
abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
// TODO: This should provided by the WM component
+
/** Provides Optional of BubbleManager */
@SysUISingleton
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt
index 991b54e8035e..ded0fb79cd6f 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt
@@ -59,7 +59,7 @@ class CutoutDecorProviderImpl(
(view as? DisplayCutoutView)?.let { cutoutView ->
cutoutView.setColor(tintColor)
cutoutView.updateRotation(rotation)
- cutoutView.onDisplayChanged(displayUniqueId)
+ cutoutView.updateConfiguration(displayUniqueId)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index ec0013bb5000..976afd457f79 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -34,8 +34,6 @@ import com.android.systemui.FaceScanningOverlay
import com.android.systemui.biometrics.AuthController
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.statusbar.StatusBarStateController
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -47,15 +45,13 @@ class FaceScanningProviderFactory @Inject constructor(
private val statusBarStateController: StatusBarStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Main private val mainExecutor: Executor,
- private val featureFlags: FeatureFlags
) : DecorProviderFactory() {
private val display = context.display
private val displayInfo = DisplayInfo()
override val hasProviders: Boolean
get() {
- if (!featureFlags.isEnabled(Flags.FACE_SCANNING_ANIM) ||
- authController.faceSensorLocation == null) {
+ if (authController.faceSensorLocation == null) {
return false
}
@@ -99,7 +95,7 @@ class FaceScanningProviderFactory @Inject constructor(
}
fun shouldShowFaceScanningAnim(): Boolean {
- return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceScanning
+ return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceDetectionRunning
}
}
@@ -124,7 +120,7 @@ class FaceScanningOverlayProviderImpl(
view.layoutParams = it
(view as? FaceScanningOverlay)?.let { overlay ->
overlay.setColor(tintColor)
- overlay.onDisplayChanged(displayUniqueId)
+ overlay.updateConfiguration(displayUniqueId)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index a25286438387..8b4aeefb6ed4 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -78,23 +78,18 @@ class RoundedCornerResDelegate(
reloadMeasures()
}
- private fun reloadAll(newReloadToken: Int) {
- if (reloadToken == newReloadToken) {
- return
- }
- reloadToken = newReloadToken
- reloadRes()
- reloadMeasures()
- }
-
fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
if (displayUniqueId != newDisplayUniqueId) {
displayUniqueId = newDisplayUniqueId
newReloadToken ?.let { reloadToken = it }
reloadRes()
reloadMeasures()
- } else {
- newReloadToken?.let { reloadAll(it) }
+ } else if (newReloadToken != null) {
+ if (reloadToken == newReloadToken) {
+ return
+ }
+ reloadToken = newReloadToken
+ reloadMeasures()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 2e51b51d2836..b69afeb37371 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -287,8 +287,8 @@ public class DozeLog implements Dumpable {
/**
* Appends sensor event dropped event to logs
*/
- public void traceSensorEventDropped(int sensorEvent, String reason) {
- mLogger.logSensorEventDropped(sensorEvent, reason);
+ public void traceSensorEventDropped(@Reason int pulseReason, String reason) {
+ mLogger.logSensorEventDropped(pulseReason, reason);
}
/**
@@ -386,6 +386,47 @@ public class DozeLog implements Dumpable {
mLogger.logSetAodDimmingScrim((long) scrimOpacity);
}
+ /**
+ * Appends sensor attempted to register and whether it was a successful registration.
+ */
+ public void traceSensorRegisterAttempt(String sensorName, boolean successfulRegistration) {
+ mLogger.logSensorRegisterAttempt(sensorName, successfulRegistration);
+ }
+
+ /**
+ * Appends sensor attempted to unregister and whether it was successfully unregistered.
+ */
+ public void traceSensorUnregisterAttempt(String sensorInfo, boolean successfullyUnregistered) {
+ mLogger.logSensorUnregisterAttempt(sensorInfo, successfullyUnregistered);
+ }
+
+ /**
+ * Appends sensor attempted to unregister and whether it was successfully unregistered
+ * with a reason the sensor is being unregistered.
+ */
+ public void traceSensorUnregisterAttempt(String sensorInfo, boolean successfullyUnregistered,
+ String reason) {
+ mLogger.logSensorUnregisterAttempt(sensorInfo, successfullyUnregistered, reason);
+ }
+
+ /**
+ * Appends the event of skipping a sensor registration since it's already registered.
+ */
+ public void traceSkipRegisterSensor(String sensorInfo) {
+ mLogger.logSkipSensorRegistration(sensorInfo);
+ }
+
+ /**
+ * Appends a plugin sensor was registered or unregistered event.
+ */
+ public void tracePluginSensorUpdate(boolean registered) {
+ if (registered) {
+ mLogger.log("register plugin sensor");
+ } else {
+ mLogger.log("unregister plugin sensor");
+ }
+ }
+
private class SummaryStats {
private int mCount;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index cc5766210406..18c8e01cbf76 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -19,12 +19,13 @@ package com.android.systemui.doze
import android.view.Display
import com.android.systemui.doze.DozeLog.Reason
import com.android.systemui.doze.DozeLog.reasonToString
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.log.dagger.DozeLog
import com.android.systemui.statusbar.policy.DevicePostureController
+import com.google.errorprone.annotations.CompileTimeConstant
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -224,10 +225,14 @@ class DozeLogger @Inject constructor(
})
}
- fun logPulseDropped(from: String, state: DozeMachine.State) {
+ /**
+ * Log why a pulse was dropped and the current doze machine state. The state can be null
+ * if the DozeMachine is the middle of transitioning between states.
+ */
+ fun logPulseDropped(from: String, state: DozeMachine.State?) {
buffer.log(TAG, INFO, {
str1 = from
- str2 = state.name
+ str2 = state?.name
}, {
"Pulse dropped, cannot pulse from=$str1 state=$str2"
})
@@ -320,6 +325,50 @@ class DozeLogger @Inject constructor(
"Doze car mode started"
})
}
+
+ fun logSensorRegisterAttempt(sensorInfo: String, successfulRegistration: Boolean) {
+ buffer.log(TAG, INFO, {
+ str1 = sensorInfo
+ bool1 = successfulRegistration
+ }, {
+ "Register sensor. Success=$bool1 sensor=$str1"
+ })
+ }
+
+ fun logSensorUnregisterAttempt(sensorInfo: String, successfulUnregister: Boolean) {
+ buffer.log(TAG, INFO, {
+ str1 = sensorInfo
+ bool1 = successfulUnregister
+ }, {
+ "Unregister sensor. Success=$bool1 sensor=$str1"
+ })
+ }
+
+ fun logSensorUnregisterAttempt(
+ sensorInfo: String,
+ successfulUnregister: Boolean,
+ reason: String
+ ) {
+ buffer.log(TAG, INFO, {
+ str1 = sensorInfo
+ bool1 = successfulUnregister
+ str2 = reason
+ }, {
+ "Unregister sensor. reason=$str2. Success=$bool1 sensor=$str1"
+ })
+ }
+
+ fun logSkipSensorRegistration(sensor: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = sensor
+ }, {
+ "Skipping sensor registration because its already registered. sensor=$str1"
+ })
+ }
+
+ fun log(@CompileTimeConstant msg: String) {
+ buffer.log(TAG, DEBUG, msg)
+ }
}
private const val TAG = "DozeLog"
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index ae412152b10e..96c35d43052e 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -20,7 +20,6 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWA
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
import android.annotation.MainThread;
-import android.app.UiModeManager;
import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Trace;
@@ -145,10 +144,9 @@ public class DozeMachine {
private final Service mDozeService;
private final WakeLock mWakeLock;
- private final AmbientDisplayConfiguration mConfig;
+ private final AmbientDisplayConfiguration mAmbientDisplayConfig;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final DozeHost mDozeHost;
- private final UiModeManager mUiModeManager;
private final DockManager mDockManager;
private final Part[] mParts;
@@ -156,18 +154,18 @@ public class DozeMachine {
private State mState = State.UNINITIALIZED;
private int mPulseReason;
private boolean mWakeLockHeldForCurrentState = false;
+ private int mUiModeType = Configuration.UI_MODE_TYPE_NORMAL;
@Inject
- public DozeMachine(@WrappedService Service service, AmbientDisplayConfiguration config,
+ public DozeMachine(@WrappedService Service service,
+ AmbientDisplayConfiguration ambientDisplayConfig,
WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle,
- UiModeManager uiModeManager,
DozeLog dozeLog, DockManager dockManager,
DozeHost dozeHost, Part[] parts) {
mDozeService = service;
- mConfig = config;
+ mAmbientDisplayConfig = ambientDisplayConfig;
mWakefulnessLifecycle = wakefulnessLifecycle;
mWakeLock = wakeLock;
- mUiModeManager = uiModeManager;
mDozeLog = dozeLog;
mDockManager = dockManager;
mDozeHost = dozeHost;
@@ -187,6 +185,18 @@ public class DozeMachine {
}
/**
+ * Notifies the {@link DozeMachine} that {@link Configuration} has changed.
+ */
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ int newUiModeType = newConfiguration.uiMode & Configuration.UI_MODE_TYPE_MASK;
+ if (mUiModeType == newUiModeType) return;
+ mUiModeType = newUiModeType;
+ for (Part part : mParts) {
+ part.onUiModeTypeChanged(mUiModeType);
+ }
+ }
+
+ /**
* Requests transitioning to {@code requestedState}.
*
* This can be called during a state transition, in which case it will be queued until all
@@ -211,6 +221,14 @@ public class DozeMachine {
requestState(State.DOZE_REQUEST_PULSE, pulseReason);
}
+ /**
+ * @return true if {@link DozeMachine} is currently in either {@link State#UNINITIALIZED}
+ * or {@link State#FINISH}
+ */
+ public boolean isUninitializedOrFinished() {
+ return mState == State.UNINITIALIZED || mState == State.FINISH;
+ }
+
void onScreenState(int state) {
mDozeLog.traceDisplayState(state);
for (Part part : mParts) {
@@ -360,7 +378,7 @@ public class DozeMachine {
if (mState == State.FINISH) {
return State.FINISH;
}
- if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
+ if (mUiModeType == Configuration.UI_MODE_TYPE_CAR
&& (requestedState.canPulse() || requestedState.staysAwake())) {
Log.i(TAG, "Doze is suppressed with all triggers disabled as car mode is active");
mDozeLog.traceCarModeStarted();
@@ -411,7 +429,7 @@ public class DozeMachine {
nextState = State.FINISH;
} else if (mDockManager.isDocked()) {
nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED;
- } else if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
+ } else if (mAmbientDisplayConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
nextState = State.DOZE_AOD;
} else {
nextState = State.DOZE;
@@ -427,6 +445,7 @@ public class DozeMachine {
/** Dumps the current state */
public void dump(PrintWriter pw) {
pw.print(" state="); pw.println(mState);
+ pw.print(" mUiModeType="); pw.println(mUiModeType);
pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState);
pw.print(" wakeLock="); pw.println(mWakeLock);
pw.println("Parts:");
@@ -459,6 +478,19 @@ public class DozeMachine {
/** Sets the {@link DozeMachine} when this Part is associated with one. */
default void setDozeMachine(DozeMachine dozeMachine) {}
+
+ /**
+ * Notifies the Part about a change in {@link Configuration#uiMode}.
+ *
+ * @param newUiModeType {@link Configuration#UI_MODE_TYPE_NORMAL},
+ * {@link Configuration#UI_MODE_TYPE_DESK},
+ * {@link Configuration#UI_MODE_TYPE_CAR},
+ * {@link Configuration#UI_MODE_TYPE_TELEVISION},
+ * {@link Configuration#UI_MODE_TYPE_APPLIANCE},
+ * {@link Configuration#UI_MODE_TYPE_WATCH},
+ * or {@link Configuration#UI_MODE_TYPE_VR_HEADSET}
+ */
+ default void onUiModeTypeChanged(int newUiModeType) {}
}
/** A wrapper interface for {@link android.service.dreams.DreamService} */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index da6c163b1eea..9a091e725818 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -23,7 +23,6 @@ import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_
import android.annotation.AnyThread;
import android.app.ActivityManager;
-import android.content.Context;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorManager;
@@ -37,7 +36,6 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
-import android.util.Log;
import android.view.Display;
import androidx.annotation.NonNull;
@@ -88,12 +86,9 @@ import java.util.function.Consumer;
* trigger callbacks on the provided {@link mProxCallback}.
*/
public class DozeSensors {
-
- private static final boolean DEBUG = DozeService.DEBUG;
private static final String TAG = "DozeSensors";
private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
- private final Context mContext;
private final AsyncSensorManager mSensorManager;
private final AmbientDisplayConfiguration mConfig;
private final WakeLock mWakeLock;
@@ -144,7 +139,6 @@ public class DozeSensors {
}
DozeSensors(
- Context context,
AsyncSensorManager sensorManager,
DozeParameters dozeParameters,
AmbientDisplayConfiguration config,
@@ -157,7 +151,6 @@ public class DozeSensors {
AuthController authController,
DevicePostureController devicePostureController
) {
- mContext = context;
mSensorManager = sensorManager;
mConfig = config;
mWakeLock = wakeLock;
@@ -605,10 +598,7 @@ public class DozeSensors {
// cancel the previous sensor:
if (mRegistered) {
final boolean rt = mSensorManager.cancelTriggerSensor(this, oldSensor);
- if (DEBUG) {
- Log.d(TAG, "posture changed, cancelTriggerSensor[" + oldSensor + "] "
- + rt);
- }
+ mDozeLog.traceSensorUnregisterAttempt(oldSensor.toString(), rt, "posture changed");
mRegistered = false;
}
@@ -654,19 +644,13 @@ public class DozeSensors {
if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) {
if (!mRegistered) {
mRegistered = mSensorManager.requestTriggerSensor(this, sensor);
- if (DEBUG) {
- Log.d(TAG, "requestTriggerSensor[" + sensor + "] " + mRegistered);
- }
+ mDozeLog.traceSensorRegisterAttempt(sensor.toString(), mRegistered);
} else {
- if (DEBUG) {
- Log.d(TAG, "requestTriggerSensor[" + sensor + "] already registered");
- }
+ mDozeLog.traceSkipRegisterSensor(sensor.toString());
}
} else if (mRegistered) {
final boolean rt = mSensorManager.cancelTriggerSensor(this, sensor);
- if (DEBUG) {
- Log.d(TAG, "cancelTriggerSensor[" + sensor + "] " + rt);
- }
+ mDozeLog.traceSensorUnregisterAttempt(sensor.toString(), rt);
mRegistered = false;
}
}
@@ -704,7 +688,6 @@ public class DozeSensors {
final Sensor sensor = mSensors[mPosture];
mDozeLog.traceSensor(mPulseReason);
mHandler.post(mWakeLock.wrap(() -> {
- if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
if (sensor != null && sensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
UI_EVENT_LOGGER.log(DozeSensorsUiEvent.ACTION_AMBIENT_GESTURE_PICKUP);
}
@@ -776,11 +759,11 @@ public class DozeSensors {
&& !mRegistered) {
asyncSensorManager.registerPluginListener(mPluginSensor, this);
mRegistered = true;
- if (DEBUG) Log.d(TAG, "registerPluginListener");
+ mDozeLog.tracePluginSensorUpdate(true /* registered */);
} else if (mRegistered) {
asyncSensorManager.unregisterPluginListener(mPluginSensor, this);
mRegistered = false;
- if (DEBUG) Log.d(TAG, "unregisterPluginListener");
+ mDozeLog.tracePluginSensorUpdate(false /* registered */);
}
}
@@ -813,10 +796,9 @@ public class DozeSensors {
mHandler.post(mWakeLock.wrap(() -> {
final long now = SystemClock.uptimeMillis();
if (now < mDebounceFrom + mDebounce) {
- Log.d(TAG, "onSensorEvent dropped: " + triggerEventToString(event));
+ mDozeLog.traceSensorEventDropped(mPulseReason, "debounce");
return;
}
- if (DEBUG) Log.d(TAG, "onSensorEvent: " + triggerEventToString(event));
mSensorCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues());
}));
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index a2eb4e3bb640..e8d7e4642e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -17,6 +17,7 @@
package com.android.systemui.doze;
import android.content.Context;
+import android.content.res.Configuration;
import android.os.PowerManager;
import android.os.SystemClock;
import android.service.dreams.DreamService;
@@ -59,6 +60,7 @@ public class DozeService extends DreamService
mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */);
DozeComponent dozeComponent = mDozeComponentBuilder.build(this);
mDozeMachine = dozeComponent.getDozeMachine();
+ mDozeMachine.onConfigurationChanged(getResources().getConfiguration());
}
@Override
@@ -127,6 +129,12 @@ public class DozeService extends DreamService
}
@Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDozeMachine.onConfigurationChanged(newConfig);
+ }
+
+ @Override
public void onRequestHideDoze() {
if (mDozeMachine != null) {
mDozeMachine.requestState(DozeMachine.State.DOZE);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
index 7ed4b35e1ee7..e6d98655b119 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
@@ -16,21 +16,13 @@
package com.android.systemui.doze;
-import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE;
-import static android.app.UiModeManager.ACTION_EXIT_CAR_MODE;
-
-import android.app.UiModeManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
+
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.PowerManager;
import android.os.UserHandle;
import android.text.TextUtils;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
@@ -43,7 +35,9 @@ import dagger.Lazy;
/**
* Handles suppressing doze on:
* 1. INITIALIZED, don't allow dozing at all when:
- * - in CAR_MODE
+ * - in CAR_MODE, in this scenario the device is asleep and won't listen for any triggers
+ * to wake up. In this state, no UI shows. Unlike other conditions, this suppression is only
+ * temporary and stops when the device exits CAR_MODE
* - device is NOT provisioned
* - there's a pending authentication
* 2. PowerSaveMode active
@@ -57,35 +51,47 @@ import dagger.Lazy;
*/
@DozeScope
public class DozeSuppressor implements DozeMachine.Part {
- private static final String TAG = "DozeSuppressor";
private DozeMachine mMachine;
private final DozeHost mDozeHost;
private final AmbientDisplayConfiguration mConfig;
private final DozeLog mDozeLog;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final UiModeManager mUiModeManager;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
- private boolean mBroadcastReceiverRegistered;
+ private boolean mIsCarModeEnabled = false;
@Inject
public DozeSuppressor(
DozeHost dozeHost,
AmbientDisplayConfiguration config,
DozeLog dozeLog,
- BroadcastDispatcher broadcastDispatcher,
- UiModeManager uiModeManager,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy) {
mDozeHost = dozeHost;
mConfig = config;
mDozeLog = dozeLog;
- mBroadcastDispatcher = broadcastDispatcher;
- mUiModeManager = uiModeManager;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
}
@Override
+ public void onUiModeTypeChanged(int newUiModeType) {
+ boolean isCarModeEnabled = newUiModeType == UI_MODE_TYPE_CAR;
+ if (mIsCarModeEnabled == isCarModeEnabled) {
+ return;
+ }
+ mIsCarModeEnabled = isCarModeEnabled;
+ // Do not handle the event if doze machine is not initialized yet.
+ // It will be handled upon initialization.
+ if (mMachine.isUninitializedOrFinished()) {
+ return;
+ }
+ if (mIsCarModeEnabled) {
+ handleCarModeStarted();
+ } else {
+ handleCarModeExited();
+ }
+ }
+
+ @Override
public void setDozeMachine(DozeMachine dozeMachine) {
mMachine = dozeMachine;
}
@@ -94,7 +100,6 @@ public class DozeSuppressor implements DozeMachine.Part {
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
switch (newState) {
case INITIALIZED:
- registerBroadcastReceiver();
mDozeHost.addCallback(mHostCallback);
checkShouldImmediatelyEndDoze();
checkShouldImmediatelySuspendDoze();
@@ -108,14 +113,12 @@ public class DozeSuppressor implements DozeMachine.Part {
@Override
public void destroy() {
- unregisterBroadcastReceiver();
mDozeHost.removeCallback(mHostCallback);
}
private void checkShouldImmediatelySuspendDoze() {
- if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
- mDozeLog.traceCarModeStarted();
- mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
+ if (mIsCarModeEnabled) {
+ handleCarModeStarted();
}
}
@@ -135,7 +138,7 @@ public class DozeSuppressor implements DozeMachine.Part {
@Override
public void dump(PrintWriter pw) {
- pw.println(" uiMode=" + mUiModeManager.getCurrentModeType());
+ pw.println(" isCarModeEnabled=" + mIsCarModeEnabled);
pw.println(" hasPendingAuth="
+ mBiometricUnlockControllerLazy.get().hasPendingAuthentication());
pw.println(" isProvisioned=" + mDozeHost.isProvisioned());
@@ -143,40 +146,18 @@ public class DozeSuppressor implements DozeMachine.Part {
pw.println(" aodPowerSaveActive=" + mDozeHost.isPowerSaveActive());
}
- private void registerBroadcastReceiver() {
- if (mBroadcastReceiverRegistered) {
- return;
- }
- IntentFilter filter = new IntentFilter(ACTION_ENTER_CAR_MODE);
- filter.addAction(ACTION_EXIT_CAR_MODE);
- mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
- mBroadcastReceiverRegistered = true;
+ private void handleCarModeExited() {
+ mDozeLog.traceCarModeEnded();
+ mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)
+ ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE);
}
- private void unregisterBroadcastReceiver() {
- if (!mBroadcastReceiverRegistered) {
- return;
- }
- mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
- mBroadcastReceiverRegistered = false;
+ private void handleCarModeStarted() {
+ mDozeLog.traceCarModeStarted();
+ mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
}
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (ACTION_ENTER_CAR_MODE.equals(action)) {
- mDozeLog.traceCarModeStarted();
- mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
- } else if (ACTION_EXIT_CAR_MODE.equals(action)) {
- mDozeLog.traceCarModeEnded();
- mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)
- ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE);
- }
- }
- };
-
- private DozeHost.Callback mHostCallback = new DozeHost.Callback() {
+ private final DozeHost.Callback mHostCallback = new DozeHost.Callback() {
@Override
public void onPowerSaveChanged(boolean active) {
// handles suppression changes, while DozeMachine#transitionPolicy handles gating
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index ef454ffbdeb1..32cb1c01b776 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -198,7 +198,7 @@ public class DozeTriggers implements DozeMachine.Part {
mAllowPulseTriggers = true;
mSessionTracker = sessionTracker;
- mDozeSensors = new DozeSensors(context, mSensorManager, dozeParameters,
+ mDozeSensors = new DozeSensors(mSensorManager, dozeParameters,
config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
secureSettings, authController, devicePostureController);
mDockManager = dockManager;
@@ -536,13 +536,13 @@ public class DozeTriggers implements DozeMachine.Part {
return;
}
- if (!mAllowPulseTriggers || mDozeHost.isPulsePending() || !canPulse()) {
+ if (!mAllowPulseTriggers || mDozeHost.isPulsePending() || !canPulse(dozeState)) {
if (!mAllowPulseTriggers) {
mDozeLog.tracePulseDropped("requestPulse - !mAllowPulseTriggers");
} else if (mDozeHost.isPulsePending()) {
mDozeLog.tracePulseDropped("requestPulse - pulsePending");
- } else if (!canPulse()) {
- mDozeLog.tracePulseDropped("requestPulse", dozeState);
+ } else if (!canPulse(dozeState)) {
+ mDozeLog.tracePulseDropped("requestPulse - dozeState cannot pulse", dozeState);
}
runIfNotNull(onPulseSuppressedListener);
return;
@@ -559,15 +559,16 @@ public class DozeTriggers implements DozeMachine.Part {
// not in pocket, continue pulsing
final boolean isPulsePending = mDozeHost.isPulsePending();
mDozeHost.setPulsePending(false);
- if (!isPulsePending || mDozeHost.isPulsingBlocked() || !canPulse()) {
+ if (!isPulsePending || mDozeHost.isPulsingBlocked() || !canPulse(dozeState)) {
if (!isPulsePending) {
mDozeLog.tracePulseDropped("continuePulseRequest - pulse no longer"
+ " pending, pulse was cancelled before it could start"
+ " transitioning to pulsing state.");
} else if (mDozeHost.isPulsingBlocked()) {
mDozeLog.tracePulseDropped("continuePulseRequest - pulsingBlocked");
- } else if (!canPulse()) {
- mDozeLog.tracePulseDropped("continuePulseRequest", mMachine.getState());
+ } else if (!canPulse(dozeState)) {
+ mDozeLog.tracePulseDropped("continuePulseRequest"
+ + " - doze state cannot pulse", dozeState);
}
runIfNotNull(onPulseSuppressedListener);
return;
@@ -582,10 +583,10 @@ public class DozeTriggers implements DozeMachine.Part {
.ifPresent(uiEventEnum -> mUiEventLogger.log(uiEventEnum, getKeyguardSessionId()));
}
- private boolean canPulse() {
- return mMachine.getState() == DozeMachine.State.DOZE
- || mMachine.getState() == DozeMachine.State.DOZE_AOD
- || mMachine.getState() == DozeMachine.State.DOZE_AOD_DOCKED;
+ private boolean canPulse(DozeMachine.State dozeState) {
+ return dozeState == DozeMachine.State.DOZE
+ || dozeState == DozeMachine.State.DOZE_AOD
+ || dozeState == DozeMachine.State.DOZE_AOD_DOCKED;
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 0ccb222c8acc..cedd850ac2ef 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -210,7 +210,8 @@ public class DreamHomeControlsComplication implements Complication {
final Intent intent = new Intent(mContext, ControlsActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(ControlsUiController.EXTRA_ANIMATE, true);
+ .putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+ .putExtra(ControlsUiController.EXIT_TO_DREAM, true);
final ActivityLaunchAnimator.Controller controller =
v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
index c07d4022df76..1166c2fc1120 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
@@ -28,7 +28,7 @@ import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaCarouselController;
+import com.android.systemui.media.controls.ui.MediaCarouselController;
import com.android.systemui.media.dream.MediaDreamComplication;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index 08ef8f3d025f..609bd76cf210 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -24,8 +24,13 @@ import com.android.systemui.R
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.dump.nano.SystemUIProtoDump
+import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
+import com.google.protobuf.nano.MessageNano
+import java.io.BufferedOutputStream
+import java.io.FileDescriptor
+import java.io.FileOutputStream
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Provider
@@ -100,7 +105,7 @@ class DumpHandler @Inject constructor(
/**
* Dump the diagnostics! Behavior can be controlled via [args].
*/
- fun dump(pw: PrintWriter, args: Array<String>) {
+ fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
Trace.beginSection("DumpManager#dump()")
val start = SystemClock.uptimeMillis()
@@ -111,10 +116,12 @@ class DumpHandler @Inject constructor(
return
}
- when (parsedArgs.dumpPriority) {
- PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs)
- PRIORITY_ARG_NORMAL -> dumpNormal(pw, parsedArgs)
- else -> dumpParameterized(pw, parsedArgs)
+ when {
+ parsedArgs.dumpPriority == PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs)
+ parsedArgs.dumpPriority == PRIORITY_ARG_NORMAL && !parsedArgs.proto -> {
+ dumpNormal(pw, parsedArgs)
+ }
+ else -> dumpParameterized(fd, pw, parsedArgs)
}
pw.println()
@@ -122,7 +129,7 @@ class DumpHandler @Inject constructor(
Trace.endSection()
}
- private fun dumpParameterized(pw: PrintWriter, args: ParsedArgs) {
+ private fun dumpParameterized(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
when (args.command) {
"bugreport-critical" -> dumpCritical(pw, args)
"bugreport-normal" -> dumpNormal(pw, args)
@@ -130,7 +137,13 @@ class DumpHandler @Inject constructor(
"buffers" -> dumpBuffers(pw, args)
"config" -> dumpConfig(pw)
"help" -> dumpHelp(pw)
- else -> dumpTargets(args.nonFlagArgs, pw, args)
+ else -> {
+ if (args.proto) {
+ dumpProtoTargets(args.nonFlagArgs, fd, args)
+ } else {
+ dumpTargets(args.nonFlagArgs, pw, args)
+ }
+ }
}
}
@@ -160,6 +173,26 @@ class DumpHandler @Inject constructor(
}
}
+ private fun dumpProtoTargets(
+ targets: List<String>,
+ fd: FileDescriptor,
+ args: ParsedArgs
+ ) {
+ val systemUIProto = SystemUIProtoDump()
+ if (targets.isNotEmpty()) {
+ for (target in targets) {
+ dumpManager.dumpProtoTarget(target, systemUIProto, args.rawArgs)
+ }
+ } else {
+ dumpManager.dumpProtoDumpables(systemUIProto, args.rawArgs)
+ }
+ val buffer = BufferedOutputStream(FileOutputStream(fd))
+ buffer.use {
+ it.write(MessageNano.toByteArray(systemUIProto))
+ it.flush()
+ }
+ }
+
private fun dumpTargets(
targets: List<String>,
pw: PrintWriter,
@@ -235,6 +268,7 @@ class DumpHandler @Inject constructor(
pw.println("$ <invocation> buffers")
pw.println("$ <invocation> bugreport-critical")
pw.println("$ <invocation> bugreport-normal")
+ pw.println("$ <invocation> config")
pw.println()
pw.println("Targets can be listed:")
@@ -266,6 +300,7 @@ class DumpHandler @Inject constructor(
}
}
}
+ PROTO -> pArgs.proto = true
"-t", "--tail" -> {
pArgs.tailLength = readArgument(iterator, arg) {
it.toInt()
@@ -277,6 +312,9 @@ class DumpHandler @Inject constructor(
"-h", "--help" -> {
pArgs.command = "help"
}
+ // This flag is passed as part of the proto dump in Bug reports, we can ignore
+ // it because this is our default behavior.
+ "-a" -> {}
else -> {
throw ArgParseException("Unknown flag: $arg")
}
@@ -313,13 +351,21 @@ class DumpHandler @Inject constructor(
const val PRIORITY_ARG_CRITICAL = "CRITICAL"
const val PRIORITY_ARG_HIGH = "HIGH"
const val PRIORITY_ARG_NORMAL = "NORMAL"
+ const val PROTO = "--proto"
}
}
private val PRIORITY_OPTIONS =
arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_HIGH, PRIORITY_ARG_NORMAL)
-private val COMMANDS = arrayOf("bugreport-critical", "bugreport-normal", "buffers", "dumpables")
+private val COMMANDS = arrayOf(
+ "bugreport-critical",
+ "bugreport-normal",
+ "buffers",
+ "dumpables",
+ "config",
+ "help"
+)
private class ParsedArgs(
val rawArgs: Array<String>,
@@ -329,6 +375,7 @@ private class ParsedArgs(
var tailLength: Int = 0
var command: String? = null
var listOnly = false
+ var proto = false
}
class ArgParseException(message: String) : Exception(message)
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index cca04da8f426..ae780896a7e2 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -18,7 +18,9 @@ package com.android.systemui.dump
import android.util.ArrayMap
import com.android.systemui.Dumpable
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.ProtoDumpable
+import com.android.systemui.dump.nano.SystemUIProtoDump
+import com.android.systemui.plugins.log.LogBuffer
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Singleton
@@ -90,7 +92,7 @@ open class DumpManager @Inject constructor() {
target: String,
pw: PrintWriter,
args: Array<String>,
- tailLength: Int
+ tailLength: Int,
) {
for (dumpable in dumpables.values) {
if (dumpable.name.endsWith(target)) {
@@ -107,6 +109,36 @@ open class DumpManager @Inject constructor() {
}
}
+ @Synchronized
+ fun dumpProtoTarget(
+ target: String,
+ protoDump: SystemUIProtoDump,
+ args: Array<String>
+ ) {
+ for (dumpable in dumpables.values) {
+ if (dumpable.dumpable is ProtoDumpable && dumpable.name.endsWith(target)) {
+ dumpProtoDumpable(dumpable.dumpable, protoDump, args)
+ return
+ }
+ }
+ }
+
+ @Synchronized
+ fun dumpProtoDumpables(
+ systemUIProtoDump: SystemUIProtoDump,
+ args: Array<String>
+ ) {
+ for (dumpable in dumpables.values) {
+ if (dumpable.dumpable is ProtoDumpable) {
+ dumpProtoDumpable(
+ dumpable.dumpable,
+ systemUIProtoDump,
+ args
+ )
+ }
+ }
+ }
+
/**
* Dumps all registered dumpables to [pw]
*/
@@ -184,6 +216,14 @@ open class DumpManager @Inject constructor() {
buffer.dumpable.dump(pw, tailLength)
}
+ private fun dumpProtoDumpable(
+ protoDumpable: ProtoDumpable,
+ systemUIProtoDump: SystemUIProtoDump,
+ args: Array<String>
+ ) {
+ protoDumpable.dumpProto(systemUIProtoDump, args)
+ }
+
private fun canAssignToNameLocked(name: String, newDumpable: Any): Boolean {
val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable
return existingDumpable == null || newDumpable == existingDumpable
@@ -195,4 +235,4 @@ private data class RegisteredDumpable<T>(
val dumpable: T
)
-private const val TAG = "DumpManager" \ No newline at end of file
+private const val TAG = "DumpManager"
diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
index 0eab1afc4119..8299b13d305f 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
@@ -19,7 +19,7 @@ package com.android.systemui.dump
import android.content.Context
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.util.io.Files
import com.android.systemui.util.time.SystemClock
import java.io.IOException
diff --git a/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java b/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java
index 0a41a56b5ecb..da983ab03a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java
+++ b/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java
@@ -51,6 +51,7 @@ public class SystemUIAuxiliaryDumpService extends Service {
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
// Simulate the NORMAL priority arg being passed to us
mDumpHandler.dump(
+ fd,
pw,
new String[] { DumpHandler.PRIORITY_ARG, DumpHandler.PRIORITY_ARG_NORMAL });
}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/sysui.proto b/packages/SystemUI/src/com/android/systemui/dump/sysui.proto
new file mode 100644
index 000000000000..cd8c08aeb2dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/sysui.proto
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+syntax = "proto3";
+
+package com.android.systemui.dump;
+
+import "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto";
+
+option java_multiple_files = true;
+
+message SystemUIProtoDump {
+ repeated com.android.systemui.qs.QsTileState tiles = 1;
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
deleted file mode 100644
index 72e0ec1dd144..000000000000
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.flags;
-
-import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
-
-import com.android.internal.annotations.Keep;
-import com.android.systemui.R;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * List of {@link Flag} objects for use in SystemUI.
- *
- * Flag Ids are integers.
- * Ids must be unique. This is enforced in a unit test.
- * Ids need not be sequential. Flags can "claim" a chunk of ids for flags in related features with
- * a comment. This is purely for organizational purposes.
- *
- * On public release builds, flags will always return their default value. There is no way to
- * change their value on release builds.
- *
- * See {@link FeatureFlagsDebug} for instructions on flipping the flags via adb.
- */
-public class Flags {
- public static final UnreleasedFlag TEAMFOOD = new UnreleasedFlag(1);
-
- /***************************************/
- // 100 - notification
- public static final UnreleasedFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
- new UnreleasedFlag(103);
-
- public static final UnreleasedFlag NSSL_DEBUG_LINES =
- new UnreleasedFlag(105);
-
- public static final UnreleasedFlag NSSL_DEBUG_REMOVE_ANIMATION =
- new UnreleasedFlag(106);
-
- public static final UnreleasedFlag NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE =
- new UnreleasedFlag(107);
-
- public static final ResourceBooleanFlag NOTIFICATION_DRAG_TO_CONTENTS =
- new ResourceBooleanFlag(108, R.bool.config_notificationToContents);
-
- public static final ReleasedFlag REMOVE_UNRANKED_NOTIFICATIONS =
- new ReleasedFlag(109);
-
- public static final UnreleasedFlag FSI_REQUIRES_KEYGUARD =
- new UnreleasedFlag(110, true);
-
- public static final UnreleasedFlag INSTANT_VOICE_REPLY = new UnreleasedFlag(111, true);
-
- public static final UnreleasedFlag NOTIFICATION_MEMORY_MONITOR_ENABLED = new UnreleasedFlag(112,
- false);
-
- public static final UnreleasedFlag NOTIFICATION_DISMISSAL_FADE = new UnreleasedFlag(113, true);
-
- // next id: 114
-
- /***************************************/
- // 200 - keyguard/lockscreen
-
- // ** Flag retired **
- // public static final BooleanFlag KEYGUARD_LAYOUT =
- // new BooleanFlag(200, true);
-
- public static final ReleasedFlag LOCKSCREEN_ANIMATIONS =
- new ReleasedFlag(201);
-
- public static final ReleasedFlag NEW_UNLOCK_SWIPE_ANIMATION =
- new ReleasedFlag(202);
-
- public static final ResourceBooleanFlag CHARGING_RIPPLE =
- new ResourceBooleanFlag(203, R.bool.flag_charging_ripple);
-
- public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER =
- new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher);
-
- public static final ResourceBooleanFlag FACE_SCANNING_ANIM =
- new ResourceBooleanFlag(205, R.bool.config_enableFaceScanningAnimation);
-
- public static final UnreleasedFlag LOCKSCREEN_CUSTOM_CLOCKS = new UnreleasedFlag(207);
-
- /**
- * Flag to enable the usage of the new bouncer data source. This is a refactor of and
- * eventual replacement of KeyguardBouncer.java.
- */
- public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208);
-
- /**
- * Whether the user interactor and repository should use `UserSwitcherController`.
- *
- * <p>If this is {@code false}, the interactor and repo skip the controller and directly access
- * the framework APIs.
- */
- public static final UnreleasedFlag USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
- new UnreleasedFlag(210);
-
- /**
- * Whether `UserSwitcherController` should use the user interactor.
- *
- * <p>When this is {@code true}, the controller does not directly access framework APIs.
- * Instead, it goes through the interactor.
- *
- * <p>Note: do not set this to true if {@link #USER_INTERACTOR_AND_REPO_USE_CONTROLLER} is
- * {@code true} as it would created a cycle between controller -> interactor -> controller.
- */
- public static final ReleasedFlag USER_CONTROLLER_USES_INTERACTOR = new ReleasedFlag(211);
-
- /***************************************/
- // 300 - power menu
- public static final ReleasedFlag POWER_MENU_LITE =
- new ReleasedFlag(300);
-
- /***************************************/
- // 400 - smartspace
- public static final ReleasedFlag SMARTSPACE_DEDUPING =
- new ReleasedFlag(400);
-
- public static final ReleasedFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
- new ReleasedFlag(401);
-
- public static final ResourceBooleanFlag SMARTSPACE =
- new ResourceBooleanFlag(402, R.bool.flag_smartspace);
-
- /***************************************/
- // 500 - quick settings
- /**
- * @deprecated Not needed anymore
- */
- @Deprecated
- public static final ReleasedFlag NEW_USER_SWITCHER =
- new ReleasedFlag(500);
-
- public static final UnreleasedFlag COMBINED_QS_HEADERS =
- new UnreleasedFlag(501, true);
-
- public static final ResourceBooleanFlag PEOPLE_TILE =
- new ResourceBooleanFlag(502, R.bool.flag_conversations);
-
- public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT =
- new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut);
-
- /**
- * @deprecated Not needed anymore
- */
- @Deprecated
- public static final ReleasedFlag NEW_FOOTER = new ReleasedFlag(504);
-
- public static final UnreleasedFlag NEW_HEADER = new UnreleasedFlag(505, true);
- public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
- new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
-
- public static final ReleasedFlag NEW_FOOTER_ACTIONS = new ReleasedFlag(507);
-
- /***************************************/
- // 600- status bar
- public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
- new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
-
- public static final ReleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE =
- new ReleasedFlag(603, false);
-
- public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_BACKEND =
- new UnreleasedFlag(604, false);
-
- public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_FRONTEND =
- new UnreleasedFlag(605, false);
-
- /***************************************/
- // 700 - dialer/calls
- public static final ReleasedFlag ONGOING_CALL_STATUS_BAR_CHIP =
- new ReleasedFlag(700);
-
- public static final ReleasedFlag ONGOING_CALL_IN_IMMERSIVE =
- new ReleasedFlag(701);
-
- public static final ReleasedFlag ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP =
- new ReleasedFlag(702);
-
- /***************************************/
- // 800 - general visual/theme
- public static final ResourceBooleanFlag MONET =
- new ResourceBooleanFlag(800, R.bool.flag_monet);
-
- /***************************************/
- // 801 - region sampling
- public static final UnreleasedFlag REGION_SAMPLING = new UnreleasedFlag(801);
-
- // 802 - wallpaper rendering
- public static final UnreleasedFlag USE_CANVAS_RENDERER = new UnreleasedFlag(802, true);
-
- // 803 - screen contents translation
- public static final UnreleasedFlag SCREEN_CONTENTS_TRANSLATION = new UnreleasedFlag(803);
-
- /***************************************/
- // 900 - media
- public static final ReleasedFlag MEDIA_TAP_TO_TRANSFER = new ReleasedFlag(900);
- public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901);
- public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903);
- public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904);
- public static final UnreleasedFlag DREAM_MEDIA_COMPLICATION = new UnreleasedFlag(905);
- public static final UnreleasedFlag DREAM_MEDIA_TAP_TO_OPEN = new UnreleasedFlag(906);
-
- // 1000 - dock
- public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING =
- new ReleasedFlag(1000);
- public static final ReleasedFlag DOCK_SETUP_ENABLED = new ReleasedFlag(1001);
-
- public static final UnreleasedFlag ROUNDED_BOX_RIPPLE =
- new UnreleasedFlag(1002, /* teamfood= */ true);
-
- // 1100 - windowing
- @Keep
- public static final SysPropBooleanFlag WM_ENABLE_SHELL_TRANSITIONS =
- new SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false);
-
- /**
- * b/170163464: animate bubbles expanded view collapse with home gesture
- */
- @Keep
- public static final SysPropBooleanFlag BUBBLES_HOME_GESTURE =
- new SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true);
-
- @Keep
- public static final DeviceConfigBooleanFlag WM_ENABLE_PARTIAL_SCREEN_SHARING =
- new DeviceConfigBooleanFlag(1102, "record_task_content",
- NAMESPACE_WINDOW_MANAGER, false, true);
-
- @Keep
- public static final SysPropBooleanFlag HIDE_NAVBAR_WINDOW =
- new SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false);
-
- @Keep
- public static final SysPropBooleanFlag WM_DESKTOP_WINDOWING =
- new SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false);
-
- @Keep
- public static final SysPropBooleanFlag WM_CAPTION_ON_SHELL =
- new SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false);
-
- @Keep
- public static final SysPropBooleanFlag FLOATING_TASKS_ENABLED =
- new SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false);
-
- @Keep
- public static final SysPropBooleanFlag SHOW_FLOATING_TASKS_AS_BUBBLES =
- new SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false);
-
- @Keep
- public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_BUBBLE =
- new SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true);
- @Keep
- public static final SysPropBooleanFlag ENABLE_FLING_TO_DISMISS_PIP =
- new SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true);
-
- @Keep
- public static final SysPropBooleanFlag ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
- new SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false);
-
- // 1200 - predictive back
- @Keep
- public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
- 1200, "persist.wm.debug.predictive_back", true);
- @Keep
- public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK_ANIM = new SysPropBooleanFlag(
- 1201, "persist.wm.debug.predictive_back_anim", false);
- @Keep
- public static final SysPropBooleanFlag WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
- new SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false);
-
- public static final UnreleasedFlag NEW_BACK_AFFORDANCE =
- new UnreleasedFlag(1203, false /* teamfood */);
-
- // 1300 - screenshots
-
- public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300);
- public static final UnreleasedFlag SCREENSHOT_WORK_PROFILE_POLICY = new UnreleasedFlag(1301);
-
- // 1400 - columbus
- public static final ReleasedFlag QUICK_TAP_IN_PCC = new ReleasedFlag(1400);
-
- // 1500 - chooser
- public static final UnreleasedFlag CHOOSER_UNBUNDLED = new UnreleasedFlag(1500);
-
- // Pay no attention to the reflection behind the curtain.
- // ========================== Curtain ==========================
- // | |
- // | . . . . . . . . . . . . . . . . . . . |
- private static Map<Integer, Flag<?>> sFlagMap;
- static Map<Integer, Flag<?>> collectFlags() {
- if (sFlagMap != null) {
- return sFlagMap;
- }
-
- Map<Integer, Flag<?>> flags = new HashMap<>();
- List<Field> flagFields = getFlagFields();
-
- for (Field field : flagFields) {
- try {
- Flag<?> flag = (Flag<?>) field.get(null);
- flags.put(flag.getId(), flag);
- } catch (IllegalAccessException e) {
- // no-op
- }
- }
-
- sFlagMap = flags;
-
- return sFlagMap;
- }
-
- static List<Field> getFlagFields() {
- Field[] fields = Flags.class.getFields();
- List<Field> result = new ArrayList<>();
-
- for (Field field : fields) {
- Class<?> t = field.getType();
- if (Flag.class.isAssignableFrom(t)) {
- result.add(field);
- }
- }
-
- return result;
- }
- // | . . . . . . . . . . . . . . . . . . . |
- // | |
- // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
new file mode 100644
index 000000000000..9ef3f5d97d47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import android.provider.DeviceConfig
+import com.android.internal.annotations.Keep
+import com.android.systemui.R
+import java.lang.reflect.Field
+
+/**
+ * List of [Flag] objects for use in SystemUI.
+ *
+ * Flag Ids are integers. Ids must be unique. This is enforced in a unit test. Ids need not be
+ * sequential. Flags can "claim" a chunk of ids for flags in related features with a comment. This
+ * is purely for organizational purposes.
+ *
+ * On public release builds, flags will always return their default value. There is no way to change
+ * their value on release builds.
+ *
+ * See [FeatureFlagsDebug] for instructions on flipping the flags via adb.
+ */
+object Flags {
+ @JvmField val TEAMFOOD = UnreleasedFlag(1)
+
+ // 100 - notification
+ // TODO(b/254512751): Tracking Bug
+ val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = UnreleasedFlag(103)
+
+ // TODO(b/254512732): Tracking Bug
+ @JvmField val NSSL_DEBUG_LINES = UnreleasedFlag(105)
+
+ // TODO(b/254512505): Tracking Bug
+ @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = UnreleasedFlag(106)
+
+ // TODO(b/254512624): Tracking Bug
+ @JvmField
+ val NOTIFICATION_DRAG_TO_CONTENTS =
+ ResourceBooleanFlag(108, R.bool.config_notificationToContents)
+
+ // TODO(b/254512517): Tracking Bug
+ val FSI_REQUIRES_KEYGUARD = UnreleasedFlag(110, teamfood = true)
+
+ // TODO(b/254512538): Tracking Bug
+ val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true)
+
+ // TODO(b/254512425): Tracking Bug
+ val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = false)
+
+ // TODO(b/254512731): Tracking Bug
+ @JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
+ val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true)
+ val SEMI_STABLE_SORT = UnreleasedFlag(115, teamfood = true)
+ @JvmField val NOTIFICATION_GROUP_CORNER = UnreleasedFlag(116, true)
+ // next id: 117
+
+ // 200 - keyguard/lockscreen
+ // ** Flag retired **
+ // public static final BooleanFlag KEYGUARD_LAYOUT =
+ // new BooleanFlag(200, true);
+ // TODO(b/254512713): Tracking Bug
+ @JvmField val LOCKSCREEN_ANIMATIONS = ReleasedFlag(201)
+
+ // TODO(b/254512750): Tracking Bug
+ val NEW_UNLOCK_SWIPE_ANIMATION = ReleasedFlag(202)
+ val CHARGING_RIPPLE = ResourceBooleanFlag(203, R.bool.flag_charging_ripple)
+
+ // TODO(b/254512281): Tracking Bug
+ @JvmField
+ val BOUNCER_USER_SWITCHER = ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher)
+
+ // TODO(b/254512676): Tracking Bug
+ @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = UnreleasedFlag(207, teamfood = true)
+
+ /**
+ * Flag to enable the usage of the new bouncer data source. This is a refactor of and eventual
+ * replacement of KeyguardBouncer.java.
+ */
+ // TODO(b/254512385): Tracking Bug
+ @JvmField val MODERN_BOUNCER = UnreleasedFlag(208)
+
+ /**
+ * Whether the user interactor and repository should use `UserSwitcherController`.
+ *
+ * If this is `false`, the interactor and repo skip the controller and directly access the
+ * framework APIs.
+ */
+ // TODO(b/254513286): Tracking Bug
+ val USER_INTERACTOR_AND_REPO_USE_CONTROLLER = UnreleasedFlag(210)
+
+ /**
+ * Whether `UserSwitcherController` should use the user interactor.
+ *
+ * When this is `true`, the controller does not directly access framework APIs. Instead, it goes
+ * through the interactor.
+ *
+ * Note: do not set this to true if [.USER_INTERACTOR_AND_REPO_USE_CONTROLLER] is `true` as it
+ * would created a cycle between controller -> interactor -> controller.
+ */
+ // TODO(b/254513102): Tracking Bug
+ val USER_CONTROLLER_USES_INTERACTOR = ReleasedFlag(211)
+
+ /**
+ * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
+ * the digits when the clock moves.
+ */
+ @JvmField val STEP_CLOCK_ANIMATION = UnreleasedFlag(212)
+
+ // 300 - power menu
+ // TODO(b/254512600): Tracking Bug
+ @JvmField val POWER_MENU_LITE = ReleasedFlag(300)
+
+ // 400 - smartspace
+
+ // TODO(b/254513100): Tracking Bug
+ val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = ReleasedFlag(401)
+ val SMARTSPACE = ResourceBooleanFlag(402, R.bool.flag_smartspace)
+
+ // 500 - quick settings
+ @Deprecated("Not needed anymore") val NEW_USER_SWITCHER = ReleasedFlag(500)
+
+ // TODO(b/254512321): Tracking Bug
+ @JvmField val COMBINED_QS_HEADERS = UnreleasedFlag(501, teamfood = true)
+ val PEOPLE_TILE = ResourceBooleanFlag(502, R.bool.flag_conversations)
+ @JvmField
+ val QS_USER_DETAIL_SHORTCUT =
+ ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut)
+
+ // TODO(b/254512699): Tracking Bug
+ @Deprecated("Not needed anymore") val NEW_FOOTER = ReleasedFlag(504)
+
+ // TODO(b/254512747): Tracking Bug
+ val NEW_HEADER = UnreleasedFlag(505, teamfood = true)
+
+ // TODO(b/254512383): Tracking Bug
+ @JvmField
+ val FULL_SCREEN_USER_SWITCHER =
+ ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher)
+
+ // TODO(b/254512678): Tracking Bug
+ @JvmField val NEW_FOOTER_ACTIONS = ReleasedFlag(507)
+
+ // 600- status bar
+ // TODO(b/254513246): Tracking Bug
+ val STATUS_BAR_USER_SWITCHER = ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip)
+
+ // TODO(b/254513025): Tracking Bug
+ val STATUS_BAR_LETTERBOX_APPEARANCE = ReleasedFlag(603, teamfood = false)
+
+ // TODO(b/254512623): Tracking Bug
+ @Deprecated("Replaced by mobile and wifi specific flags.")
+ val NEW_STATUS_BAR_PIPELINE_BACKEND = UnreleasedFlag(604, teamfood = false)
+
+ // TODO(b/254512660): Tracking Bug
+ @Deprecated("Replaced by mobile and wifi specific flags.")
+ val NEW_STATUS_BAR_PIPELINE_FRONTEND = UnreleasedFlag(605, teamfood = false)
+
+ val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606, false)
+
+ val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607, false)
+
+ // 700 - dialer/calls
+ // TODO(b/254512734): Tracking Bug
+ val ONGOING_CALL_STATUS_BAR_CHIP = ReleasedFlag(700)
+
+ // TODO(b/254512681): Tracking Bug
+ val ONGOING_CALL_IN_IMMERSIVE = ReleasedFlag(701)
+
+ // TODO(b/254512753): Tracking Bug
+ val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = ReleasedFlag(702)
+
+ // 800 - general visual/theme
+ @JvmField val MONET = ResourceBooleanFlag(800, R.bool.flag_monet)
+
+ // 801 - region sampling
+ // TODO(b/254512848): Tracking Bug
+ val REGION_SAMPLING = UnreleasedFlag(801)
+
+ // 802 - wallpaper rendering
+ // TODO(b/254512923): Tracking Bug
+ @JvmField val USE_CANVAS_RENDERER = ReleasedFlag(802)
+
+ // 803 - screen contents translation
+ // TODO(b/254513187): Tracking Bug
+ val SCREEN_CONTENTS_TRANSLATION = UnreleasedFlag(803)
+
+ // 804 - monochromatic themes
+ @JvmField val MONOCHROMATIC_THEMES = UnreleasedFlag(804)
+
+ // 900 - media
+ // TODO(b/254512697): Tracking Bug
+ val MEDIA_TAP_TO_TRANSFER = ReleasedFlag(900)
+
+ // TODO(b/254512502): Tracking Bug
+ val MEDIA_SESSION_ACTIONS = UnreleasedFlag(901)
+
+ // TODO(b/254512726): Tracking Bug
+ val MEDIA_NEARBY_DEVICES = ReleasedFlag(903)
+
+ // TODO(b/254512695): Tracking Bug
+ val MEDIA_MUTE_AWAIT = ReleasedFlag(904)
+
+ // TODO(b/254512654): Tracking Bug
+ @JvmField val DREAM_MEDIA_COMPLICATION = UnreleasedFlag(905)
+
+ // TODO(b/254512673): Tracking Bug
+ @JvmField val DREAM_MEDIA_TAP_TO_OPEN = UnreleasedFlag(906)
+
+ // TODO(b/254513168): Tracking Bug
+ val UMO_SURFACE_RIPPLE = UnreleasedFlag(907)
+
+ // 1000 - dock
+ val SIMULATE_DOCK_THROUGH_CHARGING = ReleasedFlag(1000)
+
+ // TODO(b/254512444): Tracking Bug
+ @JvmField val DOCK_SETUP_ENABLED = ReleasedFlag(1001)
+
+ // TODO(b/254512758): Tracking Bug
+ @JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
+
+ // TODO(b/254512525): Tracking Bug
+ @JvmField val REFACTORED_DOCK_SETUP = ReleasedFlag(1003, teamfood = true)
+
+ // 1100 - windowing
+ @Keep
+ val WM_ENABLE_SHELL_TRANSITIONS =
+ SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false)
+
+ /** b/170163464: animate bubbles expanded view collapse with home gesture */
+ @Keep
+ val BUBBLES_HOME_GESTURE =
+ SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true)
+
+ // TODO(b/254513207): Tracking Bug
+ @JvmField
+ @Keep
+ val WM_ENABLE_PARTIAL_SCREEN_SHARING =
+ DeviceConfigBooleanFlag(
+ 1102,
+ "record_task_content",
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ false,
+ teamfood = true
+ )
+
+ // TODO(b/254512674): Tracking Bug
+ @JvmField
+ @Keep
+ val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false)
+
+ @Keep
+ val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false)
+
+ @Keep
+ val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false)
+
+ @Keep
+ val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false)
+
+ @Keep
+ val SHOW_FLOATING_TASKS_AS_BUBBLES =
+ SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false)
+
+ @Keep
+ val ENABLE_FLING_TO_DISMISS_BUBBLE =
+ SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
+
+ @Keep
+ val ENABLE_FLING_TO_DISMISS_PIP =
+ SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true)
+
+ @Keep
+ val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
+ SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
+
+ // 1200 - predictive back
+ @Keep
+ val WM_ENABLE_PREDICTIVE_BACK =
+ SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true)
+
+ @Keep
+ val WM_ENABLE_PREDICTIVE_BACK_ANIM =
+ SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false)
+
+ @Keep
+ val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
+ SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false)
+
+ // TODO(b/254512728): Tracking Bug
+ @JvmField val NEW_BACK_AFFORDANCE = UnreleasedFlag(1203, teamfood = false)
+
+ // 1300 - screenshots
+ // TODO(b/254512719): Tracking Bug
+ @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300)
+
+ // TODO(b/254513155): Tracking Bug
+ @JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301)
+
+ // 1400 - columbus
+ // TODO(b/254512756): Tracking Bug
+ val QUICK_TAP_IN_PCC = ReleasedFlag(1400)
+
+ // 1500 - chooser
+ // TODO(b/254512507): Tracking Bug
+ val CHOOSER_UNBUNDLED = UnreleasedFlag(1500)
+
+ // 1700 - clipboard
+ @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700)
+
+ // 1800 - shade container
+ @JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, true)
+
+ // Pay no attention to the reflection behind the curtain.
+ // ========================== Curtain ==========================
+ // | |
+ // | . . . . . . . . . . . . . . . . . . . |
+ @JvmStatic
+ fun collectFlags(): Map<Int, Flag<*>> {
+ return flagFields
+ .map { field ->
+ // field[null] returns the current value of the field.
+ // See java.lang.Field#get
+ val flag = field[null] as Flag<*>
+ flag.id to flag
+ }
+ .toMap()
+ }
+
+ // | . . . . . . . . . . . . . . . . . . . |
+ @JvmStatic
+ val flagFields: List<Field>
+ get() {
+ return Flags::class.java.fields.filter { f ->
+ Flag::class.java.isAssignableFrom(f.type)
+ }
+ }
+ // | |
+ // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index da5819a7f3bc..3ef5499237f1 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -116,6 +116,7 @@ import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -448,10 +449,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
*
* @param keyguardShowing True if keyguard is showing
* @param isDeviceProvisioned True if device is provisioned
- * @param view The view from which we should animate the dialog when showing it
+ * @param expandable The expandable from which we should animate the dialog when
+ * showing it
*/
public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
- @Nullable View view) {
+ @Nullable Expandable expandable) {
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = isDeviceProvisioned;
if (mDialog != null && mDialog.isShowing()) {
@@ -463,7 +465,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mDialog.dismiss();
mDialog = null;
} else {
- handleShow(view);
+ handleShow(expandable);
}
}
@@ -495,7 +497,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
}
}
- protected void handleShow(@Nullable View view) {
+ protected void handleShow(@Nullable Expandable expandable) {
awakenIfNecessary();
mDialog = createDialog();
prepareDialog();
@@ -507,10 +509,12 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
// Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
- if (view != null) {
- mDialogLaunchAnimator.showFromView(mDialog, view,
- new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG));
+ DialogLaunchAnimator.Controller controller =
+ expandable != null ? expandable.dialogLaunchController(
+ new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG)) : null;
+ if (controller != null) {
+ mDialogLaunchAnimator.show(mDialog, controller);
} else {
mDialog.show();
}
@@ -1016,8 +1020,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
Log.w(TAG, "Bugreport handler could not be launched");
mIActivityManager.requestInteractiveBugReport();
}
- // Close shade so user sees the activity
- mCentralSurfacesOptional.ifPresent(CentralSurfaces::collapseShade);
+ // Maybe close shade (depends on a flag) so user sees the activity
+ mCentralSurfacesOptional.ifPresent(
+ CentralSurfaces::collapseShadeForBugreport);
} catch (RemoteException e) {
}
}
@@ -1036,8 +1041,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
mIActivityManager.requestFullBugReport();
- // Close shade so user sees the activity
- mCentralSurfacesOptional.ifPresent(CentralSurfaces::collapseShade);
+ // Maybe close shade (depends on a flag) so user sees the activity
+ mCentralSurfacesOptional.ifPresent(CentralSurfaces::collapseShadeForBugreport);
} catch (RemoteException e) {
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9b036c166418..c8370b4be6f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -401,6 +401,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private final float mWindowCornerRadius;
/**
+ * The duration in milliseconds of the dream open animation.
+ */
+ private final int mDreamOpenAnimationDuration;
+
+ /**
* The animation used for hiding keyguard. This is used to fetch the animation timings if
* WindowManager is not providing us with them.
*/
@@ -794,6 +799,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker =
mUpdateMonitor.getStrongAuthTracker();
int strongAuth = strongAuthTracker.getStrongAuthForUser(currentUser);
+ boolean allowedNonStrongAfterIdleTimeout =
+ strongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(currentUser);
if (any && !strongAuthTracker.hasUserAuthenticatedSinceBoot()) {
return KeyguardSecurityView.PROMPT_REASON_RESTART;
@@ -812,6 +819,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
} else if (any && (strongAuth
& STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT) != 0) {
return KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT;
+ } else if (any && !allowedNonStrongAfterIdleTimeout) {
+ return KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT;
}
return KeyguardSecurityView.PROMPT_REASON_NONE;
}
@@ -942,8 +951,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
mOccludeByDreamAnimator = ValueAnimator.ofFloat(0f, 1f);
- // Use the same duration as for the UNOCCLUDE.
- mOccludeByDreamAnimator.setDuration(UNOCCLUDE_ANIMATION_DURATION);
+ mOccludeByDreamAnimator.setDuration(mDreamOpenAnimationDuration);
mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
mOccludeByDreamAnimator.addUpdateListener(
animation -> {
@@ -1175,6 +1183,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mPowerButtonY = context.getResources().getDimensionPixelSize(
R.dimen.physical_power_button_center_screen_location_y);
mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+
+ mDreamOpenAnimationDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_dreamOpenAnimationDuration);
}
public void userActivity() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 56f1ac46a875..56a1f1ae936e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -43,6 +43,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
+import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -72,6 +73,7 @@ import dagger.Provides;
FalsingModule.class,
KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
+ StartKeyguardTransitionModule.class,
})
public class KeyguardModule {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 45b668e609ea..b186ae0ceec4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -21,6 +21,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.doze.DozeHost
+import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
@@ -85,6 +86,9 @@ interface KeyguardRepository {
*/
val dozeAmount: Flow<Float>
+ /** Observable for the [StatusBarState] */
+ val statusBarState: Flow<StatusBarState>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -185,6 +189,24 @@ constructor(
return keyguardStateController.isShowing
}
+ override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(state: Int) {
+ trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state")
+ }
+ }
+
+ statusBarStateController.addCallback(callback)
+ trySendWithFailureLogging(
+ statusBarStateIntToObject(statusBarStateController.getState()),
+ TAG,
+ "initial state"
+ )
+
+ awaitClose { statusBarStateController.removeCallback(callback) }
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.value = animate
}
@@ -197,6 +219,15 @@ constructor(
_clockPosition.value = Position(x, y)
}
+ private fun statusBarStateIntToObject(value: Int): StatusBarState {
+ return when (value) {
+ 0 -> StatusBarState.SHADE
+ 1 -> StatusBarState.KEYGUARD
+ 2 -> StatusBarState.SHADE_LOCKED
+ else -> throw IllegalArgumentException("Invalid StatusBarState value: $value")
+ }
+ }
+
companion object {
private const val TAG = "KeyguardRepositoryImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
new file mode 100644
index 000000000000..e8532ecfdc37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.data.repository
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.animation.ValueAnimator.AnimatorUpdateListener
+import android.annotation.FloatRange
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filter
+
+@SysUISingleton
+class KeyguardTransitionRepository @Inject constructor() {
+ /*
+ * Each transition between [KeyguardState]s will have an associated Flow.
+ * In order to collect these events, clients should call [transition].
+ */
+ private val _transitions = MutableStateFlow(TransitionStep())
+ val transitions = _transitions.asStateFlow()
+
+ /* Information about the active transition. */
+ private var currentTransitionInfo: TransitionInfo? = null
+ /*
+ * When manual control of the transition is requested, a unique [UUID] is used as the handle
+ * to permit calls to [updateTransition]
+ */
+ private var updateTransitionId: UUID? = null
+
+ /**
+ * Interactors that require information about changes between [KeyguardState]s will call this to
+ * register themselves for flowable [TransitionStep]s when that transition occurs.
+ */
+ fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
+ return transitions.filter { step -> step.from == from && step.to == to }
+ }
+
+ /**
+ * Begin a transition from one state to another. The [info.from] must match
+ * [currentTransitionInfo.to], or the request will be denied. This is enforced to avoid
+ * unplanned transitions.
+ */
+ fun startTransition(info: TransitionInfo): UUID? {
+ if (currentTransitionInfo != null) {
+ // Open questions:
+ // * Queue of transitions? buffer of 1?
+ // * Are transitions cancellable if a new one is triggered?
+ // * What validation does this need to do?
+ Log.wtf(TAG, "Transition still active: $currentTransitionInfo")
+ return null
+ }
+ currentTransitionInfo?.animator?.cancel()
+
+ currentTransitionInfo = info
+ info.animator?.let { animator ->
+ // An animator was provided, so use it to run the transition
+ animator.setFloatValues(0f, 1f)
+ val updateListener =
+ object : AnimatorUpdateListener {
+ override fun onAnimationUpdate(animation: ValueAnimator) {
+ emitTransition(
+ info,
+ (animation.getAnimatedValue() as Float),
+ TransitionState.RUNNING
+ )
+ }
+ }
+ val adapter =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ Log.i(TAG, "Starting transition: $info")
+ emitTransition(info, 0f, TransitionState.STARTED)
+ }
+ override fun onAnimationCancel(animation: Animator) {
+ Log.i(TAG, "Cancelling transition: $info")
+ }
+ override fun onAnimationEnd(animation: Animator) {
+ Log.i(TAG, "Ending transition: $info")
+ emitTransition(info, 1f, TransitionState.FINISHED)
+ animator.removeListener(this)
+ animator.removeUpdateListener(updateListener)
+ }
+ }
+ animator.addListener(adapter)
+ animator.addUpdateListener(updateListener)
+ animator.start()
+ return@startTransition null
+ }
+ ?: run {
+ Log.i(TAG, "Starting transition (manual): $info")
+ emitTransition(info, 0f, TransitionState.STARTED)
+
+ // No animator, so it's manual. Provide a mechanism to callback
+ updateTransitionId = UUID.randomUUID()
+ return@startTransition updateTransitionId
+ }
+ }
+
+ /**
+ * Allows manual control of a transition. When calling [startTransition], the consumer must pass
+ * in a null animator. In return, it will get a unique [UUID] that will be validated to allow
+ * further updates.
+ *
+ * When the transition is over, TransitionState.FINISHED must be passed into the [state]
+ * parameter.
+ */
+ fun updateTransition(
+ transitionId: UUID,
+ @FloatRange(from = 0.0, to = 1.0) value: Float,
+ state: TransitionState
+ ) {
+ if (updateTransitionId != transitionId) {
+ Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
+ return
+ }
+
+ if (currentTransitionInfo == null) {
+ Log.wtf(TAG, "Attempting to update with null 'currentTransitionInfo'")
+ return
+ }
+
+ currentTransitionInfo?.let { info ->
+ if (state == TransitionState.FINISHED) {
+ updateTransitionId = null
+ Log.i(TAG, "Ending transition: $info")
+ }
+
+ emitTransition(info, value, state)
+ }
+ }
+
+ private fun emitTransition(
+ info: TransitionInfo,
+ value: Float,
+ transitionState: TransitionState
+ ) {
+ if (transitionState == TransitionState.FINISHED) {
+ currentTransitionInfo = null
+ }
+ _transitions.value = TransitionStep(info.from, info.to, value, transitionState)
+ }
+
+ companion object {
+ private const val TAG = "KeyguardTransitionRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
new file mode 100644
index 000000000000..400376663f1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class AodLockscreenTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardRepository: KeyguardRepository,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor("AOD<->LOCKSCREEN") {
+
+ override fun start() {
+ scope.launch {
+ keyguardRepository.isDozing.collect { isDozing ->
+ if (isDozing) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.AOD,
+ getAnimator(),
+ )
+ )
+ } else {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 500L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
index 7d4db37c6b0f..2af9318d92ec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
@@ -273,8 +273,8 @@ constructor(
/** Tell the bouncer to start the pre hide animation. */
fun startDisappearAnimation(runnable: Runnable) {
val finishRunnable = Runnable {
- repository.setStartDisappearAnimation(null)
runnable.run()
+ repository.setStartDisappearAnimation(null)
}
repository.setStartDisappearAnimation(finishRunnable)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 192919e32cf6..fc2269c6b01c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -38,7 +38,7 @@ constructor(
val dozeAmount: Flow<Float> = repository.dozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
- /** Whether the keyguard is showing ot not. */
+ /** Whether the keyguard is showing to not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
fun isKeyguardShowing(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
new file mode 100644
index 000000000000..b166681433a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.domain.interactor
+
+import android.util.Log
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import java.util.Set
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardTransitionCoreStartable
+@Inject
+constructor(
+ private val interactors: Set<TransitionInteractor>,
+) : CoreStartable {
+
+ override fun start() {
+ // By listing the interactors in a when, the compiler will help enforce all classes
+ // extending the sealed class [TransitionInteractor] will be initialized.
+ interactors.forEach {
+ // `when` needs to be an expression in order for the compiler to enforce it being
+ // exhaustive
+ val ret =
+ when (it) {
+ is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
+ is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
+ }
+ it.start()
+ }
+ }
+
+ companion object {
+ private const val TAG = "KeyguardTransitionCoreStartable"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
new file mode 100644
index 000000000000..59bb22786917
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business-logic related to the keyguard transitions. */
+@SysUISingleton
+class KeyguardTransitionInteractor
+@Inject
+constructor(
+ repository: KeyguardTransitionRepository,
+) {
+ /** AOD->LOCKSCREEN transition information. */
+ val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
new file mode 100644
index 000000000000..3c2a12e3836a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class LockscreenBouncerTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardRepository: KeyguardRepository,
+ private val shadeRepository: ShadeRepository,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor("LOCKSCREEN<->BOUNCER") {
+
+ private var transitionId: UUID? = null
+
+ override fun start() {
+ scope.launch {
+ shadeRepository.shadeModel.sample(
+ combine(
+ keyguardTransitionRepository.transitions,
+ keyguardRepository.statusBarState,
+ ) { transitions, statusBarState ->
+ Pair(transitions, statusBarState)
+ }
+ ) { shadeModel, pair ->
+ val (transitions, statusBarState) = pair
+
+ val id = transitionId
+ if (id != null) {
+ // An existing `id` means a transition is started, and calls to
+ // `updateTransition` will control it until FINISHED
+ keyguardTransitionRepository.updateTransition(
+ id,
+ shadeModel.expansionAmount,
+ if (shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f) {
+ transitionId = null
+ TransitionState.FINISHED
+ } else {
+ TransitionState.RUNNING
+ }
+ )
+ } else {
+ // TODO (b/251849525): Remove statusbarstate check when that state is integrated
+ // into KeyguardTransitionRepository
+ val isOnLockscreen =
+ transitions.transitionState == TransitionState.FINISHED &&
+ transitions.to == KeyguardState.LOCKSCREEN
+ if (
+ isOnLockscreen &&
+ shadeModel.isUserDragging &&
+ statusBarState != SHADE_LOCKED
+ ) {
+ transitionId =
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.BOUNCER,
+ animator = null,
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
new file mode 100644
index 000000000000..74c542c0043f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+abstract class StartKeyguardTransitionModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(KeyguardTransitionCoreStartable::class)
+ abstract fun bind(impl: KeyguardTransitionCoreStartable): CoreStartable
+
+ @Binds
+ @IntoSet
+ abstract fun lockscreenBouncer(
+ impl: LockscreenBouncerTransitionInteractor
+ ): TransitionInteractor
+
+ @Binds
+ @IntoSet
+ abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
new file mode 100644
index 000000000000..a2a46d9e3a71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+/**
+ * Each TransitionInteractor is responsible for determining under which conditions to notify
+ * [KeyguardTransitionRepository] to signal a transition. When (and if) the transition occurs is
+ * determined by [KeyguardTransitionRepository].
+ *
+ * [name] field should be a unique identifiable string representing this state, used primarily for
+ * logging
+ *
+ * MUST list implementing classes in dagger module [StartKeyguardTransitionModule] and also in the
+ * 'when' clause of [KeyguardTransitionCoreStartable]
+ */
+sealed class TransitionInteractor(val name: String) {
+
+ abstract fun start()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
new file mode 100644
index 000000000000..f66d5d3650c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** List of all possible states to transition to/from */
+enum class KeyguardState {
+ /** For initialization only */
+ NONE,
+ /* Always-on Display. The device is in a low-power mode with a minimal UI visible */
+ AOD,
+ /*
+ * The security screen prompt UI, containing PIN, Password, Pattern, and all FPS
+ * (Fingerprint Sensor) variations, for the user to verify their credentials
+ */
+ BOUNCER,
+ /*
+ * Device is actively displaying keyguard UI and is not in low-power mode. Device may be
+ * unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
+ */
+ LOCKSCREEN,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt
new file mode 100644
index 000000000000..bb953477583d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/StatusBarState.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** See [com.android.systemui.statusbar.StatusBarState] for definitions */
+enum class StatusBarState {
+ SHADE,
+ KEYGUARD,
+ SHADE_LOCKED,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
new file mode 100644
index 000000000000..bfccf3fe076c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+import android.animation.ValueAnimator
+
+/** Tracks who is controlling the current transition, and how to run it. */
+data class TransitionInfo(
+ val ownerName: String,
+ val from: KeyguardState,
+ val to: KeyguardState,
+ val animator: ValueAnimator?, // 'null' animator signal manual control
+) {
+ override fun toString(): String =
+ "TransitionInfo(ownerName=$ownerName, from=$from, to=$to, " +
+ (if (animator != null) {
+ "animated"
+ } else {
+ "manual"
+ }) +
+ ")"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
new file mode 100644
index 000000000000..d8691c17f53d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** Possible states for a running transition between [State] */
+enum class TransitionState {
+ NONE,
+ STARTED,
+ RUNNING,
+ FINISHED
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
new file mode 100644
index 000000000000..688ec912aac8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */
+data class TransitionStep(
+ val from: KeyguardState = KeyguardState.NONE,
+ val to: KeyguardState = KeyguardState.NONE,
+ val value: Float = 0f, // constrained [0.0, 1.0]
+ val transitionState: TransitionState = TransitionState.NONE,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index 5651399cb891..f9e341c8629a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -19,6 +19,9 @@ package com.android.systemui.log
import android.app.ActivityManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
+
import javax.inject.Inject
@SysUISingleton
@@ -26,7 +29,7 @@ class LogBufferFactory @Inject constructor(
private val dumpManager: DumpManager,
private val logcatEchoTracker: LogcatEchoTracker
) {
- /* limit the size of maxPoolSize for low ram (Go) devices */
+ /* limitiometricMessageDeferralLogger the size of maxPoolSize for low ram (Go) devices */
private fun adjustMaxSize(requestedMaxSize: Int): Int {
return if (ActivityManager.isLowRamDeviceStatic()) {
minOf(requestedMaxSize, 20) /* low ram max log size*/
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
index 7f1ad6d20c16..eeadf406060d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
@@ -23,7 +23,7 @@ import java.lang.annotation.RetentionPolicy;
import javax.inject.Qualifier;
/**
- * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as
+ * A {@link com.android.systemui.plugins.log.LogBuffer} for BiometricMessages processing such as
* {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}
*/
@Qualifier
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
index 7d1f1c2709fa..5cca1ab2abe7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
index 9ca0293fbd86..1d016d837b02 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
index 7c5f4025117f..c9f78bcdeef8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
new file mode 100644
index 000000000000..0645236226bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.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.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.plugins.log.LogBuffer] for keyguard clock logs. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyguardClockLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
index 08d969b5eb77..76d20bea4bdf 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 1b81e88e62ba..9adb855d66ac 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -22,11 +22,11 @@ import android.os.Looper;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogBufferFactory;
-import com.android.systemui.log.LogcatEchoTracker;
-import com.android.systemui.log.LogcatEchoTrackerDebug;
-import com.android.systemui.log.LogcatEchoTrackerProd;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogcatEchoTracker;
+import com.android.systemui.plugins.log.LogcatEchoTrackerDebug;
+import com.android.systemui.plugins.log.LogcatEchoTrackerProd;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.util.Compile;
@@ -43,7 +43,7 @@ public class LogModule {
@SysUISingleton
@DozeLog
public static LogBuffer provideDozeLogBuffer(LogBufferFactory factory) {
- return factory.create("DozeLog", 120);
+ return factory.create("DozeLog", 150);
}
/** Provides a logging buffer for all logs related to the data layer of notifications. */
@@ -250,7 +250,7 @@ public class LogModule {
/**
* Provides a buffer for our connections and disconnections to MediaBrowserService.
*
- * See {@link com.android.systemui.media.ResumeMediaBrowser}.
+ * See {@link com.android.systemui.media.controls.resume.ResumeMediaBrowser}.
*/
@Provides
@SysUISingleton
@@ -262,7 +262,7 @@ public class LogModule {
/**
* Provides a buffer for updates to the media carousel.
*
- * See {@link com.android.systemui.media.MediaCarouselController}.
+ * See {@link com.android.systemui.media.controls.ui.MediaCarouselController}.
*/
@Provides
@SysUISingleton
@@ -316,6 +316,16 @@ public class LogModule {
}
/**
+ * Provides a {@link LogBuffer} for keyguard clock logs.
+ */
+ @Provides
+ @SysUISingleton
+ @KeyguardClockLog
+ public static LogBuffer provideKeyguardClockLog(LogBufferFactory factory) {
+ return factory.create("KeyguardClockLog", 500);
+ }
+
+ /**
* Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
index 1d7ba94af4ed..af433476b38c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -26,7 +26,7 @@ import java.lang.annotation.Retention;
import javax.inject.Qualifier;
/**
- * A {@link LogBuffer} for {@link com.android.systemui.media.ResumeMediaBrowser}
+ * A {@link LogBuffer} for {@link com.android.systemui.media.controls.resume.ResumeMediaBrowser}
*/
@Qualifier
@Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
index b03655a543f7..f4dac6efe371 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -26,7 +26,7 @@ import java.lang.annotation.Retention;
import javax.inject.Qualifier;
/**
- * A {@link LogBuffer} for {@link com.android.systemui.media.MediaCarouselController}
+ * A {@link LogBuffer} for {@link com.android.systemui.media.controls.ui.MediaCarouselController}
*/
@Qualifier
@Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
index c67d8bebe313..73690ab6c24d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
index 53963fc8d431..0c2cd92d1bb0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -26,7 +26,7 @@ import java.lang.annotation.Retention;
import javax.inject.Qualifier;
/**
- * A {@link LogBuffer} for {@link com.android.systemui.media.MediaTimeoutLogger}
+ * A {@link LogBuffer} for {@link com.android.systemui.media.controls.pipeline.MediaTimeoutLogger}
*/
@Qualifier
@Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
index 5c572e8ef554..1570d434bc62 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
index edab8c319f87..bf216c6991d2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
index 75a34fc22c3c..5b7f4bb103b4 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -26,7 +26,7 @@ import java.lang.annotation.Retention;
import javax.inject.Qualifier;
/**
- * A {@link LogBuffer} for {@link com.android.systemui.media.MediaViewLogger}
+ * A {@link LogBuffer} for {@link com.android.systemui.media.controls.ui.MediaViewLogger}
*/
@Qualifier
@Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
index b1c6dcfcb13b..6d91f0c97c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
index 20fc6ff445a6..26af4964f7b8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
index fcc184a317b8..61daf9c8d71c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
index 760fbf3928b6..a59afa0fed1b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
index a0b686487bec..6f8ea7ff2e9b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
index 8c8753a07339..835d3490293c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
index 7259eebf19b6..6e2bd7b2e1b5 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
index e96e532f94bf..77b1bf5fd630 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
index 557a254e5c09..9fd166b759d2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
index dd5010cf39a8..dd168bac5654 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
index bd0d298ebdee..d24bfcb88188 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
index b237f2d74483..67cdb722055b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
index f26b3164f488..af0f7c518e64 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
index dd6837563a74..4c276e2bfaab 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
index 8671dbfdf1fe..ba8b27c23ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
@@ -18,7 +18,7 @@ package com.android.systemui.log.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
deleted file mode 100644
index 556560c3534c..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
+++ /dev/null
@@ -1,209 +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.media
-
-import android.animation.ArgbEvaluator
-import android.animation.ValueAnimator
-import android.animation.ValueAnimator.AnimatorUpdateListener
-import android.content.Context
-import android.content.res.ColorStateList
-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
-
-/**
- * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
- * is triggered.
- */
-interface ColorTransition {
- fun updateColorScheme(scheme: ColorScheme?): Boolean
-}
-
-/**
- * A [ColorTransition] that animates between two specific colors.
- * It uses a ValueAnimator to execute the animation and interpolate between the source color and
- * the target color.
- *
- * Selection of the target color from the scheme, and application of the interpolated color
- * are delegated to callbacks.
- */
-open class AnimatingColorTransition(
- private val defaultColor: Int,
- private val extractColor: (ColorScheme) -> Int,
- private val applyColor: (Int) -> Unit
-) : AnimatorUpdateListener, ColorTransition {
-
- private val argbEvaluator = ArgbEvaluator()
- private val valueAnimator = buildAnimator()
- var sourceColor: Int = defaultColor
- var currentColor: Int = defaultColor
- var targetColor: Int = defaultColor
-
- override fun onAnimationUpdate(animation: ValueAnimator) {
- currentColor = argbEvaluator.evaluate(
- animation.animatedFraction, sourceColor, targetColor
- ) as Int
- applyColor(currentColor)
- }
-
- override fun updateColorScheme(scheme: ColorScheme?): Boolean {
- val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme)
- if (newTargetColor != targetColor) {
- sourceColor = currentColor
- targetColor = newTargetColor
- valueAnimator.cancel()
- valueAnimator.start()
- return true
- }
- return false
- }
-
- init {
- applyColor(defaultColor)
- }
-
- @VisibleForTesting
- open fun buildAnimator(): ValueAnimator {
- val animator = ValueAnimator.ofFloat(0f, 1f)
- animator.duration = 333
- animator.addUpdateListener(this)
- return animator
- }
-}
-
-typealias AnimatingColorTransitionFactory =
- (Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition
-
-/**
- * ColorSchemeTransition constructs a ColorTransition for each color in the scheme
- * that needs to be transitioned when changed. It also sets up the assignment functions for sending
- * the sending the interpolated colors to the appropriate views.
- */
-class ColorSchemeTransition internal constructor(
- private val context: Context,
- private val mediaViewHolder: MediaViewHolder,
- animatingColorTransitionFactory: AnimatingColorTransitionFactory
-) {
- constructor(context: Context, mediaViewHolder: MediaViewHolder) :
- this(context, mediaViewHolder, ::AnimatingColorTransition)
-
- val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
- val surfaceColor = animatingColorTransitionFactory(
- bgColor,
- ::surfaceFromScheme
- ) { surfaceColor ->
- val colorList = ColorStateList.valueOf(surfaceColor)
- mediaViewHolder.seamlessIcon.imageTintList = colorList
- mediaViewHolder.seamlessText.setTextColor(surfaceColor)
- mediaViewHolder.albumView.backgroundTintList = colorList
- mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor)
- }
-
- val accentPrimary = animatingColorTransitionFactory(
- loadDefaultColor(R.attr.textColorPrimary),
- ::accentPrimaryFromScheme
- ) { accentPrimary ->
- val accentColorList = ColorStateList.valueOf(accentPrimary)
- mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
- mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
- }
-
- val accentSecondary = animatingColorTransitionFactory(
- loadDefaultColor(R.attr.textColorPrimary),
- ::accentSecondaryFromScheme
- ) { accentSecondary ->
- val colorList = ColorStateList.valueOf(accentSecondary)
- (mediaViewHolder.seamlessButton.background as? RippleDrawable)?.let {
- it.setColor(colorList)
- it.effectColor = colorList
- }
- }
-
- val colorSeamless = animatingColorTransitionFactory(
- loadDefaultColor(R.attr.textColorPrimary),
- { colorScheme: ColorScheme ->
- // A1-100 dark in dark theme, A1-200 in light theme
- if (context.resources.configuration.uiMode and
- Configuration.UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES)
- colorScheme.accent1[2]
- else colorScheme.accent1[3]
- }, { seamlessColor: Int ->
- val accentColorList = ColorStateList.valueOf(seamlessColor)
- mediaViewHolder.seamlessButton.backgroundTintList = accentColorList
- })
-
- val textPrimary = animatingColorTransitionFactory(
- loadDefaultColor(R.attr.textColorPrimary),
- ::textPrimaryFromScheme
- ) { textPrimary ->
- mediaViewHolder.titleText.setTextColor(textPrimary)
- val textColorList = ColorStateList.valueOf(textPrimary)
- mediaViewHolder.seekBar.thumb.setTintList(textColorList)
- mediaViewHolder.seekBar.progressTintList = textColorList
- mediaViewHolder.scrubbingElapsedTimeView.setTextColor(textColorList)
- mediaViewHolder.scrubbingTotalTimeView.setTextColor(textColorList)
- for (button in mediaViewHolder.getTransparentActionButtons()) {
- button.imageTintList = textColorList
- }
- mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary)
- }
-
- val textPrimaryInverse = animatingColorTransitionFactory(
- loadDefaultColor(R.attr.textColorPrimaryInverse),
- ::textPrimaryInverseFromScheme
- ) { textPrimaryInverse ->
- mediaViewHolder.actionPlayPause.imageTintList = ColorStateList.valueOf(textPrimaryInverse)
- }
-
- val textSecondary = animatingColorTransitionFactory(
- loadDefaultColor(R.attr.textColorSecondary),
- ::textSecondaryFromScheme
- ) { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) }
-
- val textTertiary = animatingColorTransitionFactory(
- loadDefaultColor(R.attr.textColorTertiary),
- ::textTertiaryFromScheme
- ) { textTertiary ->
- mediaViewHolder.seekBar.progressBackgroundTintList = ColorStateList.valueOf(textTertiary)
- }
-
- val colorTransitions = arrayOf(
- surfaceColor,
- colorSeamless,
- accentPrimary,
- accentSecondary,
- textPrimary,
- textPrimaryInverse,
- textSecondary,
- textTertiary,
- )
-
- private fun loadDefaultColor(id: Int): Int {
- return Utils.getColorAttr(context, id).defaultColor
- }
-
- fun updateColorScheme(colorScheme: ColorScheme?): Boolean {
- var anyChanged = false
- colorTransitions.forEach { anyChanged = it.updateColorScheme(colorScheme) || anyChanged }
- colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme }
- return anyChanged
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
deleted file mode 100644
index 80bff83d03a0..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ /dev/null
@@ -1,1143 +0,0 @@
-package com.android.systemui.media
-
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.res.ColorStateList
-import android.content.res.Configuration
-import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
-import android.util.Log
-import android.util.MathUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.animation.PathInterpolator
-import android.widget.LinearLayout
-import androidx.annotation.VisibleForTesting
-import com.android.internal.logging.InstanceId
-import com.android.systemui.Dumpable
-import com.android.systemui.R
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.PageIndicator
-import com.android.systemui.shared.system.SysUiStatsLog
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.Utils
-import com.android.systemui.util.animation.UniqueObjectHostView
-import com.android.systemui.util.animation.requiresRemeasuring
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.time.SystemClock
-import com.android.systemui.util.traceSection
-import java.io.PrintWriter
-import java.util.TreeMap
-import javax.inject.Inject
-import javax.inject.Provider
-
-private const val TAG = "MediaCarouselController"
-private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS)
-private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
-
-/**
- * Class that is responsible for keeping the view carousel up to date.
- * This also handles changes in state and applies them to the media carousel like the expansion.
- */
-@SysUISingleton
-class MediaCarouselController @Inject constructor(
- private val context: Context,
- private val mediaControlPanelFactory: Provider<MediaControlPanel>,
- private val visualStabilityProvider: VisualStabilityProvider,
- private val mediaHostStatesManager: MediaHostStatesManager,
- private val activityStarter: ActivityStarter,
- private val systemClock: SystemClock,
- @Main executor: DelayableExecutor,
- private val mediaManager: MediaDataManager,
- configurationController: ConfigurationController,
- falsingCollector: FalsingCollector,
- falsingManager: FalsingManager,
- dumpManager: DumpManager,
- private val logger: MediaUiEventLogger,
- private val debugLogger: MediaCarouselControllerLogger
-) : Dumpable {
- /**
- * The current width of the carousel
- */
- private var currentCarouselWidth: Int = 0
-
- /**
- * The current height of the carousel
- */
- private var currentCarouselHeight: Int = 0
-
- /**
- * Are we currently showing only active players
- */
- private var currentlyShowingOnlyActive: Boolean = false
-
- /**
- * Is the player currently visible (at the end of the transformation
- */
- private var playersVisible: Boolean = false
- /**
- * The desired location where we'll be at the end of the transformation. Usually this matches
- * the end location, except when we're still waiting on a state update call.
- */
- @MediaLocation
- private var desiredLocation: Int = -1
-
- /**
- * The ending location of the view where it ends when all animations and transitions have
- * finished
- */
- @MediaLocation
- @VisibleForTesting
- var currentEndLocation: Int = -1
-
- /**
- * The ending location of the view where it ends when all animations and transitions have
- * finished
- */
- @MediaLocation
- private var currentStartLocation: Int = -1
-
- /**
- * The progress of the transition or 1.0 if there is no transition happening
- */
- private var currentTransitionProgress: Float = 1.0f
-
- /**
- * The measured width of the carousel
- */
- private var carouselMeasureWidth: Int = 0
-
- /**
- * The measured height of the carousel
- */
- private var carouselMeasureHeight: Int = 0
- private var desiredHostState: MediaHostState? = null
- private val mediaCarousel: MediaScrollView
- val mediaCarouselScrollHandler: MediaCarouselScrollHandler
- val mediaFrame: ViewGroup
- @VisibleForTesting
- lateinit var settingsButton: View
- private set
- private val mediaContent: ViewGroup
- @VisibleForTesting
- val pageIndicator: PageIndicator
- private val visualStabilityCallback: OnReorderingAllowedListener
- private var needsReordering: Boolean = false
- private var keysNeedRemoval = mutableSetOf<String>()
- var shouldScrollToActivePlayer: Boolean = false
- private var isRtl: Boolean = false
- set(value) {
- if (value != field) {
- field = value
- mediaFrame.layoutDirection =
- if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR
- mediaCarouselScrollHandler.scrollToStart()
- }
- }
- private var currentlyExpanded = true
- set(value) {
- if (field != value) {
- field = value
- for (player in MediaPlayerData.players()) {
- player.setListening(field)
- }
- }
- }
-
- companion object {
- const val ANIMATION_BASE_DURATION = 2200f
- const val DURATION = 167f
- const val DETAILS_DELAY = 1067f
- const val CONTROLS_DELAY = 1400f
- const val PAGINATION_DELAY = 1900f
- const val MEDIATITLES_DELAY = 1000f
- const val MEDIACONTAINERS_DELAY = 967f
- val TRANSFORM_BEZIER = PathInterpolator (0.68F, 0F, 0F, 1F)
- val REVERSE_BEZIER = PathInterpolator (0F, 0.68F, 1F, 0F)
-
- fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float {
- val transformStartFraction = delay / ANIMATION_BASE_DURATION
- val transformDurationFraction = duration / ANIMATION_BASE_DURATION
- val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction)
- return MathUtils.constrain((squishinessToTime - transformStartFraction) /
- transformDurationFraction, 0F, 1F)
- }
- }
-
- private val configListener = object : ConfigurationController.ConfigurationListener {
- override fun onDensityOrFontScaleChanged() {
- // System font changes should only happen when UMO is offscreen or a flicker may occur
- updatePlayers(recreateMedia = true)
- inflateSettingsButton()
- }
-
- override fun onThemeChanged() {
- updatePlayers(recreateMedia = false)
- inflateSettingsButton()
- }
-
- override fun onConfigChanged(newConfig: Configuration?) {
- if (newConfig == null) return
- isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
- }
-
- override fun onUiModeChanged() {
- updatePlayers(recreateMedia = false)
- inflateSettingsButton()
- }
- }
-
- /**
- * Update MediaCarouselScrollHandler.visibleToUser to reflect media card container visibility.
- * It will be called when the container is out of view.
- */
- lateinit var updateUserVisibility: () -> Unit
- lateinit var updateHostVisibility: () -> Unit
-
- private val isReorderingAllowed: Boolean
- get() = visualStabilityProvider.isReorderingAllowed
-
- init {
- dumpManager.registerDumpable(TAG, this)
- mediaFrame = inflateMediaCarousel()
- mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
- pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
- mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
- executor, this::onSwipeToDismiss, this::updatePageIndicatorLocation,
- this::closeGuts, falsingCollector, falsingManager, this::logSmartspaceImpression,
- logger)
- isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
- inflateSettingsButton()
- mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
- configurationController.addCallback(configListener)
- visualStabilityCallback = OnReorderingAllowedListener {
- if (needsReordering) {
- needsReordering = false
- reorderAllPlayers(previousVisiblePlayerKey = null)
- }
-
- keysNeedRemoval.forEach {
- removePlayer(it)
- }
- if (keysNeedRemoval.size > 0) {
- // Carousel visibility may need to be updated after late removals
- updateHostVisibility()
- }
- keysNeedRemoval.clear()
-
- // Update user visibility so that no extra impression will be logged when
- // activeMediaIndex resets to 0
- if (this::updateUserVisibility.isInitialized) {
- updateUserVisibility()
- }
-
- // Let's reset our scroll position
- mediaCarouselScrollHandler.scrollToStart()
- }
- visualStabilityProvider.addPersistentReorderingAllowedListener(visualStabilityCallback)
- mediaManager.addListener(object : MediaDataManager.Listener {
- override fun onMediaDataLoaded(
- key: String,
- oldKey: String?,
- data: MediaData,
- immediately: Boolean,
- receivedSmartspaceCardLatency: Int,
- isSsReactivated: Boolean
- ) {
- debugLogger.logMediaLoaded(key)
- if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
- // Log card received if a new resumable media card is added
- MediaPlayerData.getMediaPlayer(key)?.let {
- /* ktlint-disable max-line-length */
- logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
- it.mSmartspaceId,
- it.mUid,
- surfaces = intArrayOf(
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY),
- rank = MediaPlayerData.getMediaPlayerIndex(key))
- /* ktlint-disable max-line-length */
- }
- if (mediaCarouselScrollHandler.visibleToUser &&
- mediaCarouselScrollHandler.visibleMediaIndex
- == MediaPlayerData.getMediaPlayerIndex(key)) {
- logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
- }
- } else if (receivedSmartspaceCardLatency != 0) {
- // Log resume card received if resumable media card is reactivated and
- // resume card is ranked first
- MediaPlayerData.players().forEachIndexed { index, it ->
- if (it.recommendationViewHolder == null) {
- it.mSmartspaceId = SmallHash.hash(it.mUid +
- systemClock.currentTimeMillis().toInt())
- it.mIsImpressed = false
- /* ktlint-disable max-line-length */
- logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
- it.mSmartspaceId,
- it.mUid,
- surfaces = intArrayOf(
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY),
- rank = index,
- receivedLatencyMillis = receivedSmartspaceCardLatency)
- /* ktlint-disable max-line-length */
- }
- }
- // If media container area already visible to the user, log impression for
- // reactivated card.
- if (mediaCarouselScrollHandler.visibleToUser &&
- !mediaCarouselScrollHandler.qsExpanded) {
- logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
- }
- }
-
- val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active
- if (canRemove && !Utils.useMediaResumption(context)) {
- // This view isn't playing, let's remove this! This happens e.g when
- // dismissing/timing out a view. We still have the data around because
- // resumption could be on, but we should save the resources and release this.
- if (isReorderingAllowed) {
- onMediaDataRemoved(key)
- } else {
- keysNeedRemoval.add(key)
- }
- } else {
- keysNeedRemoval.remove(key)
- }
- }
-
- override fun onSmartspaceMediaDataLoaded(
- key: String,
- data: SmartspaceMediaData,
- shouldPrioritize: Boolean
- ) {
- debugLogger.logRecommendationLoaded(key)
- // Log the case where the hidden media carousel with the existed inactive resume
- // media is shown by the Smartspace signal.
- if (data.isActive) {
- val hasActivatedExistedResumeMedia =
- !mediaManager.hasActiveMedia() &&
- mediaManager.hasAnyMedia() &&
- shouldPrioritize
- if (hasActivatedExistedResumeMedia) {
- // Log resume card received if resumable media card is reactivated and
- // recommendation card is valid and ranked first
- MediaPlayerData.players().forEachIndexed { index, it ->
- if (it.recommendationViewHolder == null) {
- it.mSmartspaceId = SmallHash.hash(it.mUid +
- systemClock.currentTimeMillis().toInt())
- it.mIsImpressed = false
- /* ktlint-disable max-line-length */
- logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
- it.mSmartspaceId,
- it.mUid,
- surfaces = intArrayOf(
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY),
- rank = index,
- receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis).toInt())
- /* ktlint-disable max-line-length */
- }
- }
- }
- addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
- MediaPlayerData.getMediaPlayer(key)?.let {
- /* ktlint-disable max-line-length */
- logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
- it.mSmartspaceId,
- it.mUid,
- surfaces = intArrayOf(
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY),
- rank = MediaPlayerData.getMediaPlayerIndex(key),
- receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis).toInt())
- /* ktlint-disable max-line-length */
- }
- if (mediaCarouselScrollHandler.visibleToUser &&
- mediaCarouselScrollHandler.visibleMediaIndex
- == MediaPlayerData.getMediaPlayerIndex(key)) {
- logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
- }
- } else {
- onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
- }
- }
-
- override fun onMediaDataRemoved(key: String) {
- debugLogger.logMediaRemoved(key)
- removePlayer(key)
- }
-
- override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
- debugLogger.logRecommendationRemoved(key, immediately)
- if (immediately || isReorderingAllowed) {
- removePlayer(key)
- if (!immediately) {
- // Although it wasn't requested, we were able to process the removal
- // immediately since reordering is allowed. So, notify hosts to update
- if (this@MediaCarouselController::updateHostVisibility.isInitialized) {
- updateHostVisibility()
- }
- }
- } else {
- keysNeedRemoval.add(key)
- }
- }
- })
- mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
- // The pageIndicator is not laid out yet when we get the current state update,
- // Lets make sure we have the right dimensions
- updatePageIndicatorLocation()
- }
- mediaHostStatesManager.addCallback(object : MediaHostStatesManager.Callback {
- override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) {
- if (location == desiredLocation) {
- onDesiredLocationChanged(desiredLocation, mediaHostState, animate = false)
- }
- }
- })
- }
-
- private fun inflateSettingsButton() {
- val settings = LayoutInflater.from(context).inflate(R.layout.media_carousel_settings_button,
- mediaFrame, false) as View
- if (this::settingsButton.isInitialized) {
- mediaFrame.removeView(settingsButton)
- }
- settingsButton = settings
- mediaFrame.addView(settingsButton)
- mediaCarouselScrollHandler.onSettingsButtonUpdated(settings)
- settingsButton.setOnClickListener {
- logger.logCarouselSettings()
- activityStarter.startActivity(settingsIntent, true /* dismissShade */)
- }
- }
-
- private fun inflateMediaCarousel(): ViewGroup {
- val mediaCarousel = LayoutInflater.from(context).inflate(R.layout.media_carousel,
- UniqueObjectHostView(context), false) as ViewGroup
- // Because this is inflated when not attached to the true view hierarchy, it resolves some
- // potential issues to force that the layout direction is defined by the locale
- // (rather than inherited from the parent, which would resolve to LTR when unattached).
- mediaCarousel.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
- return mediaCarousel
- }
-
- private fun reorderAllPlayers(previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?) {
- mediaContent.removeAllViews()
- for (mediaPlayer in MediaPlayerData.players()) {
- mediaPlayer.mediaViewHolder?.let {
- mediaContent.addView(it.player)
- } ?: mediaPlayer.recommendationViewHolder?.let {
- mediaContent.addView(it.recommendations)
- }
- }
- mediaCarouselScrollHandler.onPlayersChanged()
-
- // Automatically scroll to the active player if needed
- if (shouldScrollToActivePlayer) {
- shouldScrollToActivePlayer = false
- val activeMediaIndex = MediaPlayerData.firstActiveMediaIndex()
- if (activeMediaIndex != -1) {
- previousVisiblePlayerKey?.let {
- val previousVisibleIndex = MediaPlayerData.playerKeys()
- .indexOfFirst { key -> it == key }
- mediaCarouselScrollHandler
- .scrollToPlayer(previousVisibleIndex, activeMediaIndex)
- } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = activeMediaIndex)
- }
- }
- }
-
- // Returns true if new player is added
- private fun addOrUpdatePlayer(
- key: String,
- oldKey: String?,
- data: MediaData,
- isSsReactivated: Boolean
- ): Boolean = traceSection("MediaCarouselController#addOrUpdatePlayer") {
- MediaPlayerData.moveIfExists(oldKey, key)
- val existingPlayer = MediaPlayerData.getMediaPlayer(key)
- val curVisibleMediaKey = MediaPlayerData.playerKeys()
- .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
- val isCurVisibleMediaPlaying = curVisibleMediaKey?.data?.isPlaying
- if (existingPlayer == null) {
- val newPlayer = mediaControlPanelFactory.get()
- newPlayer.attachPlayer(MediaViewHolder.create(
- LayoutInflater.from(context), mediaContent))
- newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
- val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT)
- newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
- newPlayer.bindPlayer(data, key)
- newPlayer.setListening(currentlyExpanded)
- MediaPlayerData.addMediaPlayer(
- key, data, newPlayer, systemClock, isSsReactivated, debugLogger
- )
- updatePlayerToState(newPlayer, noAnimation = true)
- if (data.active) {
- reorderAllPlayers(curVisibleMediaKey)
- } else {
- needsReordering = true
- }
- } else {
- existingPlayer.bindPlayer(data, key)
- MediaPlayerData.addMediaPlayer(
- key, data, existingPlayer, systemClock, isSsReactivated, debugLogger
- )
- // Check the playing status of both current visible and new media players
- // To make sure we scroll to the active playing media card.
- if (isReorderingAllowed ||
- shouldScrollToActivePlayer &&
- data.isPlaying == true &&
- isCurVisibleMediaPlaying == false
- ) {
- reorderAllPlayers(curVisibleMediaKey)
- } else {
- needsReordering = true
- }
- }
- updatePageIndicator()
- mediaCarouselScrollHandler.onPlayersChanged()
- mediaFrame.requiresRemeasuring = true
- // Check postcondition: mediaContent should have the same number of children as there are
- // elements in mediaPlayers.
- if (MediaPlayerData.players().size != mediaContent.childCount) {
- Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
- }
- return existingPlayer == null
- }
-
- private fun addSmartspaceMediaRecommendations(
- key: String,
- data: SmartspaceMediaData,
- shouldPrioritize: Boolean
- ) = traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") {
- if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel")
- if (MediaPlayerData.getMediaPlayer(key) != null) {
- Log.w(TAG, "Skip adding smartspace target in carousel")
- return
- }
-
- val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
- existingSmartspaceMediaKey?.let {
- val removedPlayer = MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey)
- removedPlayer?.run { debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey) }
- }
-
- val newRecs = mediaControlPanelFactory.get()
- newRecs.attachRecommendation(
- RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent))
- newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
- val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT)
- newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
- newRecs.bindRecommendation(data)
- val curVisibleMediaKey = MediaPlayerData.playerKeys()
- .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
- MediaPlayerData.addMediaRecommendation(
- key, data, newRecs, shouldPrioritize, systemClock, debugLogger
- )
- updatePlayerToState(newRecs, noAnimation = true)
- reorderAllPlayers(curVisibleMediaKey)
- updatePageIndicator()
- mediaFrame.requiresRemeasuring = true
- // Check postcondition: mediaContent should have the same number of children as there are
- // elements in mediaPlayers.
- if (MediaPlayerData.players().size != mediaContent.childCount) {
- Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
- }
- }
-
- fun removePlayer(
- key: String,
- dismissMediaData: Boolean = true,
- dismissRecommendation: Boolean = true
- ) {
- if (key == MediaPlayerData.smartspaceMediaKey()) {
- MediaPlayerData.smartspaceMediaData?.let {
- logger.logRecommendationRemoved(it.packageName, it.instanceId)
- }
- }
- val removed = MediaPlayerData.removeMediaPlayer(key)
- removed?.apply {
- mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
- mediaContent.removeView(removed.mediaViewHolder?.player)
- mediaContent.removeView(removed.recommendationViewHolder?.recommendations)
- removed.onDestroy()
- mediaCarouselScrollHandler.onPlayersChanged()
- updatePageIndicator()
-
- if (dismissMediaData) {
- // Inform the media manager of a potentially late dismissal
- mediaManager.dismissMediaData(key, delay = 0L)
- }
- if (dismissRecommendation) {
- // Inform the media manager of a potentially late dismissal
- mediaManager.dismissSmartspaceRecommendation(key, delay = 0L)
- }
- }
- }
-
- private fun updatePlayers(recreateMedia: Boolean) {
- pageIndicator.tintList = ColorStateList.valueOf(
- context.getColor(R.color.media_paging_indicator)
- )
-
- MediaPlayerData.mediaData().forEach { (key, data, isSsMediaRec) ->
- if (isSsMediaRec) {
- val smartspaceMediaData = MediaPlayerData.smartspaceMediaData
- removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
- smartspaceMediaData?.let {
- addSmartspaceMediaRecommendations(
- it.targetId, it, MediaPlayerData.shouldPrioritizeSs)
- }
- } else {
- val isSsReactivated = MediaPlayerData.isSsReactivated(key)
- if (recreateMedia) {
- removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
- }
- addOrUpdatePlayer(
- key = key, oldKey = null, data = data, isSsReactivated = isSsReactivated)
- }
- }
- }
-
- private fun updatePageIndicator() {
- val numPages = mediaContent.getChildCount()
- pageIndicator.setNumPages(numPages)
- if (numPages == 1) {
- pageIndicator.setLocation(0f)
- }
- updatePageIndicatorAlpha()
- }
-
- /**
- * Set a new interpolated state for all players. This is a state that is usually controlled
- * by a finger movement where the user drags from one state to the next.
- *
- * @param startLocation the start location of our state or -1 if this is directly set
- * @param endLocation the ending location of our state.
- * @param progress the progress of the transition between startLocation and endlocation. If
- * this is not a guided transformation, this will be 1.0f
- * @param immediately should this state be applied immediately, canceling all animations?
- */
- fun setCurrentState(
- @MediaLocation startLocation: Int,
- @MediaLocation endLocation: Int,
- progress: Float,
- immediately: Boolean
- ) {
- if (startLocation != currentStartLocation ||
- endLocation != currentEndLocation ||
- progress != currentTransitionProgress ||
- immediately
- ) {
- currentStartLocation = startLocation
- currentEndLocation = endLocation
- currentTransitionProgress = progress
- for (mediaPlayer in MediaPlayerData.players()) {
- updatePlayerToState(mediaPlayer, immediately)
- }
- maybeResetSettingsCog()
- updatePageIndicatorAlpha()
- }
- }
-
- @VisibleForTesting
- fun updatePageIndicatorAlpha() {
- val hostStates = mediaHostStatesManager.mediaHostStates
- val endIsVisible = hostStates[currentEndLocation]?.visible ?: false
- val startIsVisible = hostStates[currentStartLocation]?.visible ?: false
- val startAlpha = if (startIsVisible) 1.0f else 0.0f
- // when squishing in split shade, only use endState, which keeps changing
- // to provide squishFraction
- val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F
- val endAlpha = (if (endIsVisible) 1.0f else 0.0f) *
- calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION)
- var alpha = 1.0f
- if (!endIsVisible || !startIsVisible) {
- var progress = currentTransitionProgress
- if (!endIsVisible) {
- progress = 1.0f - progress
- }
- // Let's fade in quickly at the end where the view is visible
- progress = MathUtils.constrain(
- MathUtils.map(0.95f, 1.0f, 0.0f, 1.0f, progress),
- 0.0f,
- 1.0f)
- alpha = MathUtils.lerp(startAlpha, endAlpha, progress)
- }
- pageIndicator.alpha = alpha
- }
-
- private fun updatePageIndicatorLocation() {
- // Update the location of the page indicator, carousel clipping
- val translationX = if (isRtl) {
- (pageIndicator.width - currentCarouselWidth) / 2.0f
- } else {
- (currentCarouselWidth - pageIndicator.width) / 2.0f
- }
- pageIndicator.translationX = translationX + mediaCarouselScrollHandler.contentTranslation
- val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams
- pageIndicator.translationY = (currentCarouselHeight - pageIndicator.height -
- layoutParams.bottomMargin).toFloat()
- }
-
- /**
- * Update the dimension of this carousel.
- */
- private fun updateCarouselDimensions() {
- var width = 0
- var height = 0
- for (mediaPlayer in MediaPlayerData.players()) {
- val controller = mediaPlayer.mediaViewController
- // When transitioning the view to gone, the view gets smaller, but the translation
- // Doesn't, let's add the translation
- width = Math.max(width, controller.currentWidth + controller.translationX.toInt())
- height = Math.max(height, controller.currentHeight + controller.translationY.toInt())
- }
- if (width != currentCarouselWidth || height != currentCarouselHeight) {
- currentCarouselWidth = width
- currentCarouselHeight = height
- mediaCarouselScrollHandler.setCarouselBounds(
- currentCarouselWidth, currentCarouselHeight)
- updatePageIndicatorLocation()
- updatePageIndicatorAlpha()
- }
- }
-
- private fun maybeResetSettingsCog() {
- val hostStates = mediaHostStatesManager.mediaHostStates
- val endShowsActive = hostStates[currentEndLocation]?.showsOnlyActiveMedia
- ?: true
- val startShowsActive = hostStates[currentStartLocation]?.showsOnlyActiveMedia
- ?: endShowsActive
- if (currentlyShowingOnlyActive != endShowsActive ||
- ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
- startShowsActive != endShowsActive)) {
- // Whenever we're transitioning from between differing states or the endstate differs
- // we reset the translation
- currentlyShowingOnlyActive = endShowsActive
- mediaCarouselScrollHandler.resetTranslation(animate = true)
- }
- }
-
- private fun updatePlayerToState(mediaPlayer: MediaControlPanel, noAnimation: Boolean) {
- mediaPlayer.mediaViewController.setCurrentState(
- startLocation = currentStartLocation,
- endLocation = currentEndLocation,
- transitionProgress = currentTransitionProgress,
- applyImmediately = noAnimation)
- }
-
- /**
- * The desired location of this view has changed. We should remeasure the view to match
- * the new bounds and kick off bounds animations if necessary.
- * If an animation is happening, an animation is kicked of externally, which sets a new
- * current state until we reach the targetState.
- *
- * @param desiredLocation the location we're going to
- * @param desiredHostState the target state we're transitioning to
- * @param animate should this be animated
- */
- fun onDesiredLocationChanged(
- desiredLocation: Int,
- desiredHostState: MediaHostState?,
- animate: Boolean,
- duration: Long = 200,
- startDelay: Long = 0
- ) = traceSection("MediaCarouselController#onDesiredLocationChanged") {
- desiredHostState?.let {
- if (this.desiredLocation != desiredLocation) {
- // Only log an event when location changes
- logger.logCarouselPosition(desiredLocation)
- }
-
- // This is a hosting view, let's remeasure our players
- this.desiredLocation = desiredLocation
- this.desiredHostState = it
- currentlyExpanded = it.expansion > 0
-
- val shouldCloseGuts = !currentlyExpanded &&
- !mediaManager.hasActiveMediaOrRecommendation() &&
- desiredHostState.showsOnlyActiveMedia
-
- for (mediaPlayer in MediaPlayerData.players()) {
- if (animate) {
- mediaPlayer.mediaViewController.animatePendingStateChange(
- duration = duration,
- delay = startDelay)
- }
- if (shouldCloseGuts && mediaPlayer.mediaViewController.isGutsVisible) {
- mediaPlayer.closeGuts(!animate)
- }
-
- mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation)
- }
- mediaCarouselScrollHandler.showsSettingsButton = !it.showsOnlyActiveMedia
- mediaCarouselScrollHandler.falsingProtectionNeeded = it.falsingProtectionNeeded
- val nowVisible = it.visible
- if (nowVisible != playersVisible) {
- playersVisible = nowVisible
- if (nowVisible) {
- mediaCarouselScrollHandler.resetTranslation()
- }
- }
- updateCarouselSize()
- }
- }
-
- fun closeGuts(immediate: Boolean = true) {
- MediaPlayerData.players().forEach {
- it.closeGuts(immediate)
- }
- }
-
- /**
- * Update the size of the carousel, remeasuring it if necessary.
- */
- private fun updateCarouselSize() {
- val width = desiredHostState?.measurementInput?.width ?: 0
- val height = desiredHostState?.measurementInput?.height ?: 0
- if (width != carouselMeasureWidth && width != 0 ||
- height != carouselMeasureHeight && height != 0) {
- carouselMeasureWidth = width
- carouselMeasureHeight = height
- val playerWidthPlusPadding = carouselMeasureWidth +
- context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
- // Let's remeasure the carousel
- val widthSpec = desiredHostState?.measurementInput?.widthMeasureSpec ?: 0
- val heightSpec = desiredHostState?.measurementInput?.heightMeasureSpec ?: 0
- mediaCarousel.measure(widthSpec, heightSpec)
- mediaCarousel.layout(0, 0, width, mediaCarousel.measuredHeight)
- // Update the padding after layout; view widths are used in RTL to calculate scrollX
- mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding
- }
- }
-
- /**
- * Log the user impression for media card at visibleMediaIndex.
- */
- fun logSmartspaceImpression(qsExpanded: Boolean) {
- val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
- if (MediaPlayerData.players().size > visibleMediaIndex) {
- val mediaControlPanel = MediaPlayerData.players().elementAt(visibleMediaIndex)
- val hasActiveMediaOrRecommendationCard =
- MediaPlayerData.hasActiveMediaOrRecommendationCard()
- if (!hasActiveMediaOrRecommendationCard && !qsExpanded) {
- // Skip logging if on LS or QQS, and there is no active media card
- return
- }
- logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
- mediaControlPanel.mSmartspaceId,
- mediaControlPanel.mUid,
- intArrayOf(mediaControlPanel.surfaceForSmartspaceLogging))
- mediaControlPanel.mIsImpressed = true
- }
- }
-
- @JvmOverloads
- /**
- * Log Smartspace events
- *
- * @param eventId UI event id (e.g. 800 for SMARTSPACE_CARD_SEEN)
- * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new
- * instanceId
- * @param uid uid for the application that media comes from
- * @param surfaces list of display surfaces the media card is on (e.g. lockscreen, shade) when
- * the event happened
- * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1
- * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc.
- * @param interactedSubcardCardinality how many media items were shown to the user when there
- * is user interaction
- * @param rank the rank for media card in the media carousel, starting from 0
- * @param receivedLatencyMillis latency in milliseconds for card received events. E.g. latency
- * between headphone connection to sysUI displays media recommendation card
- * @param isSwipeToDismiss whether is to log swipe-to-dismiss event
- *
- */
- fun logSmartspaceCardReported(
- eventId: Int,
- instanceId: Int,
- uid: Int,
- surfaces: IntArray,
- interactedSubcardRank: Int = 0,
- interactedSubcardCardinality: Int = 0,
- rank: Int = mediaCarouselScrollHandler.visibleMediaIndex,
- receivedLatencyMillis: Int = 0,
- isSwipeToDismiss: Boolean = false
- ) {
- if (MediaPlayerData.players().size <= rank) {
- return
- }
-
- val mediaControlKey = MediaPlayerData.playerKeys().elementAt(rank)
- // Only log media resume card when Smartspace data is available
- if (!mediaControlKey.isSsMediaRec &&
- !mediaManager.smartspaceMediaData.isActive &&
- MediaPlayerData.smartspaceMediaData == null) {
- return
- }
-
- val cardinality = mediaContent.getChildCount()
- surfaces.forEach { surface ->
- /* ktlint-disable max-line-length */
- SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
- eventId,
- instanceId,
- // Deprecated, replaced with AiAi feature type so we don't need to create logging
- // card type for each new feature.
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
- surface,
- // Use -1 as rank value to indicate user swipe to dismiss the card
- if (isSwipeToDismiss) -1 else rank,
- cardinality,
- if (mediaControlKey.isSsMediaRec)
- 15 // MEDIA_RECOMMENDATION
- else if (mediaControlKey.isSsReactivated)
- 43 // MEDIA_RESUME_SS_ACTIVATED
- else
- 31, // MEDIA_RESUME
- uid,
- interactedSubcardRank,
- interactedSubcardCardinality,
- receivedLatencyMillis,
- null, // Media cards cannot have subcards.
- null // Media cards don't have dimensions today.
- )
- /* ktlint-disable max-line-length */
- if (DEBUG) {
- Log.d(TAG, "Log Smartspace card event id: $eventId instance id: $instanceId" +
- " surface: $surface rank: $rank cardinality: $cardinality " +
- "isRecommendationCard: ${mediaControlKey.isSsMediaRec} " +
- "isSsReactivated: ${mediaControlKey.isSsReactivated}" +
- "uid: $uid " +
- "interactedSubcardRank: $interactedSubcardRank " +
- "interactedSubcardCardinality: $interactedSubcardCardinality " +
- "received_latency_millis: $receivedLatencyMillis")
- }
- }
- }
-
- private fun onSwipeToDismiss() {
- MediaPlayerData.players().forEachIndexed {
- index, it ->
- if (it.mIsImpressed) {
- logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT,
- it.mSmartspaceId,
- it.mUid,
- intArrayOf(it.surfaceForSmartspaceLogging),
- rank = index,
- isSwipeToDismiss = true)
- // Reset card impressed state when swipe to dismissed
- it.mIsImpressed = false
- }
- }
- logger.logSwipeDismiss()
- mediaManager.onSwipeToDismiss()
- }
-
- fun getCurrentVisibleMediaContentIntent(): PendingIntent? {
- return MediaPlayerData.playerKeys()
- .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)?.data?.clickIntent
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.apply {
- println("keysNeedRemoval: $keysNeedRemoval")
- println("dataKeys: ${MediaPlayerData.dataKeys()}")
- println("playerSortKeys: ${MediaPlayerData.playerKeys()}")
- println("smartspaceMediaData: ${MediaPlayerData.smartspaceMediaData}")
- println("shouldPrioritizeSs: ${MediaPlayerData.shouldPrioritizeSs}")
- println("current size: $currentCarouselWidth x $currentCarouselHeight")
- println("location: $desiredLocation")
- println("state: ${desiredHostState?.expansion}, " +
- "only active ${desiredHostState?.showsOnlyActiveMedia}")
- }
- }
-}
-
-@VisibleForTesting
-internal object MediaPlayerData {
- private val EMPTY = MediaData(
- userId = -1,
- initialized = false,
- app = null,
- appIcon = null,
- artist = null,
- song = null,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
- packageName = "INVALID",
- token = null,
- clickIntent = null,
- device = null,
- active = true,
- resumeAction = null,
- instanceId = InstanceId.fakeInstanceId(-1),
- appUid = -1)
- // Whether should prioritize Smartspace card.
- internal var shouldPrioritizeSs: Boolean = false
- private set
- internal var smartspaceMediaData: SmartspaceMediaData? = null
- private set
-
- data class MediaSortKey(
- val isSsMediaRec: Boolean, // Whether the item represents a Smartspace media recommendation.
- val data: MediaData,
- val updateTime: Long = 0,
- val isSsReactivated: Boolean = false
- )
-
- private val comparator = compareByDescending<MediaSortKey> {
- it.data.isPlaying == true && it.data.playbackLocation == MediaData.PLAYBACK_LOCAL }
- .thenByDescending {
- it.data.isPlaying == true && it.data.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL }
- .thenByDescending { it.data.active }
- .thenByDescending { shouldPrioritizeSs == it.isSsMediaRec }
- .thenByDescending { !it.data.resumption }
- .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
- .thenByDescending { it.data.lastActive }
- .thenByDescending { it.updateTime }
- .thenByDescending { it.data.notificationKey }
-
- private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
- private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
-
- fun addMediaPlayer(
- key: String,
- data: MediaData,
- player: MediaControlPanel,
- clock: SystemClock,
- isSsReactivated: Boolean,
- debugLogger: MediaCarouselControllerLogger? = null
- ) {
- val removedPlayer = removeMediaPlayer(key)
- if (removedPlayer != null && removedPlayer != player) {
- debugLogger?.logPotentialMemoryLeak(key)
- }
- val sortKey = MediaSortKey(isSsMediaRec = false,
- data, clock.currentTimeMillis(), isSsReactivated = isSsReactivated)
- mediaData.put(key, sortKey)
- mediaPlayers.put(sortKey, player)
- }
-
- fun addMediaRecommendation(
- key: String,
- data: SmartspaceMediaData,
- player: MediaControlPanel,
- shouldPrioritize: Boolean,
- clock: SystemClock,
- debugLogger: MediaCarouselControllerLogger? = null
- ) {
- shouldPrioritizeSs = shouldPrioritize
- val removedPlayer = removeMediaPlayer(key)
- if (removedPlayer != null && removedPlayer != player) {
- debugLogger?.logPotentialMemoryLeak(key)
- }
- val sortKey = MediaSortKey(isSsMediaRec = true,
- EMPTY.copy(isPlaying = false), clock.currentTimeMillis(), isSsReactivated = true)
- mediaData.put(key, sortKey)
- mediaPlayers.put(sortKey, player)
- smartspaceMediaData = data
- }
-
- fun moveIfExists(
- oldKey: String?,
- newKey: String,
- debugLogger: MediaCarouselControllerLogger? = null
- ) {
- if (oldKey == null || oldKey == newKey) {
- return
- }
-
- mediaData.remove(oldKey)?.let {
- val removedPlayer = removeMediaPlayer(newKey)
- removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) }
- mediaData.put(newKey, it)
- }
- }
-
- fun getMediaPlayer(key: String): MediaControlPanel? {
- return mediaData.get(key)?.let { mediaPlayers.get(it) }
- }
-
- fun getMediaPlayerIndex(key: String): Int {
- val sortKey = mediaData.get(key)
- mediaPlayers.entries.forEachIndexed { index, e ->
- if (e.key == sortKey) {
- return index
- }
- }
- return -1
- }
-
- fun removeMediaPlayer(key: String) = mediaData.remove(key)?.let {
- if (it.isSsMediaRec) {
- smartspaceMediaData = null
- }
- mediaPlayers.remove(it)
- }
-
- fun mediaData() = mediaData.entries.map { e -> Triple(e.key, e.value.data, e.value.isSsMediaRec) }
-
- fun dataKeys() = mediaData.keys
-
- fun players() = mediaPlayers.values
-
- fun playerKeys() = mediaPlayers.keys
-
- /** Returns the index of the first non-timeout media. */
- fun firstActiveMediaIndex(): Int {
- mediaPlayers.entries.forEachIndexed { index, e ->
- if (!e.key.isSsMediaRec && e.key.data.active) {
- return index
- }
- }
- return -1
- }
-
- /** Returns the existing Smartspace target id. */
- fun smartspaceMediaKey(): String? {
- mediaData.entries.forEach { e ->
- if (e.value.isSsMediaRec) {
- return e.key
- }
- }
- return null
- }
-
- @VisibleForTesting
- fun clear() {
- mediaData.clear()
- mediaPlayers.clear()
- }
-
- /* Returns true if there is active media player card or recommendation card */
- fun hasActiveMediaOrRecommendationCard(): Boolean {
- if (smartspaceMediaData != null && smartspaceMediaData?.isActive!!) {
- return true
- }
- if (firstActiveMediaIndex() != -1) {
- return true
- }
- return false
- }
-
- fun isSsReactivated(key: String): Boolean = mediaData.get(key)?.isSsReactivated ?: false
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 1ac2a078c8a0..be357ee0ff73 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -182,8 +182,7 @@ class MediaProjectionAppSelectorActivity(
override fun shouldGetOnlyDefaultActivities() = false
- // TODO(b/240924732) flip the flag when the recents selector is ready
- override fun shouldShowContentPreview() = false
+ override fun shouldShowContentPreview() = true
override fun createContentPreviewView(parent: ViewGroup): ViewGroup =
recentsViewController.createView(parent)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
deleted file mode 100644
index d9c58c0d0d76..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt
+++ /dev/null
@@ -1,162 +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.media
-
-import android.media.session.PlaybackState
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.MediaTimeoutListenerLog
-import javax.inject.Inject
-
-private const val TAG = "MediaTimeout"
-
-/**
- * A buffered log for [MediaTimeoutListener] events
- */
-@SysUISingleton
-class MediaTimeoutLogger @Inject constructor(
- @MediaTimeoutListenerLog private val buffer: LogBuffer
-) {
- fun logReuseListener(key: String) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = key
- },
- {
- "reuse listener: $str1"
- }
- )
-
- fun logMigrateListener(oldKey: String?, newKey: String?, hadListener: Boolean) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = oldKey
- str2 = newKey
- bool1 = hadListener
- },
- {
- "migrate from $str1 to $str2, had listener? $bool1"
- }
- )
-
- fun logUpdateListener(key: String, wasPlaying: Boolean) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = key
- bool1 = wasPlaying
- },
- {
- "updating $str1, was playing? $bool1"
- }
- )
-
- fun logDelayedUpdate(key: String) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = key
- },
- {
- "deliver delayed playback state for $str1"
- }
- )
-
- fun logSessionDestroyed(key: String) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = key
- },
- {
- "session destroyed $str1"
- }
- )
-
- fun logPlaybackState(key: String, state: PlaybackState?) = buffer.log(
- TAG,
- LogLevel.VERBOSE,
- {
- str1 = key
- str2 = state?.toString()
- },
- {
- "state update: key=$str1 state=$str2"
- }
- )
-
- fun logStateCallback(key: String) = buffer.log(
- TAG,
- LogLevel.VERBOSE,
- {
- str1 = key
- },
- {
- "dispatching state update for $key"
- }
- )
-
- fun logScheduleTimeout(key: String, playing: Boolean, resumption: Boolean) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = key
- bool1 = playing
- bool2 = resumption
- },
- {
- "schedule timeout $str1, playing=$bool1 resumption=$bool2"
- }
- )
-
- fun logCancelIgnored(key: String) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = key
- },
- {
- "cancellation already exists for $str1"
- }
- )
-
- fun logTimeout(key: String) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = key
- },
- {
- "execute timeout for $str1"
- }
- )
-
- fun logTimeoutCancelled(key: String, reason: String) = buffer.log(
- TAG,
- LogLevel.VERBOSE,
- {
- str1 = key
- str2 = reason
- },
- {
- "media timeout cancelled for $str1, reason: $str2"
- }
- )
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt
index 73240b54a27a..531506771459 100644
--- a/packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.models
import android.content.res.ColorStateList
import android.util.Log
@@ -23,6 +23,9 @@ import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import com.android.systemui.R
+import com.android.systemui.media.controls.ui.accentPrimaryFromScheme
+import com.android.systemui.media.controls.ui.surfaceFromScheme
+import com.android.systemui.media.controls.ui.textPrimaryFromScheme
import com.android.systemui.monet.ColorScheme
/**
@@ -95,11 +98,6 @@ class GutsViewHolder constructor(itemView: View) {
}
companion object {
- val ids = setOf(
- R.id.remove_text,
- R.id.cancel,
- R.id.dismiss,
- R.id.settings
- )
+ val ids = setOf(R.id.remove_text, R.id.cancel, R.id.dismiss, R.id.settings)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index 5b2cda038bbd..f006442906e7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.player
import android.app.PendingIntent
import android.graphics.drawable.Drawable
@@ -27,69 +27,42 @@ import com.android.systemui.R
data class MediaData(
val userId: Int,
val initialized: Boolean = false,
- /**
- * App name that will be displayed on the player.
- */
+ /** App name that will be displayed on the player. */
val app: String?,
- /**
- * App icon shown on player.
- */
+ /** App icon shown on player. */
val appIcon: Icon?,
- /**
- * Artist name.
- */
+ /** Artist name. */
val artist: CharSequence?,
- /**
- * Song name.
- */
+ /** Song name. */
val song: CharSequence?,
- /**
- * Album artwork.
- */
+ /** Album artwork. */
val artwork: Icon?,
- /**
- * List of generic action buttons for the media player, based on notification actions
- */
+ /** List of generic action buttons for the media player, based on notification actions */
val actions: List<MediaAction>,
- /**
- * Same as above, but shown on smaller versions of the player, like in QQS or keyguard.
- */
+ /** Same as above, but shown on smaller versions of the player, like in QQS or keyguard. */
val actionsToShowInCompact: List<Int>,
/**
- * Semantic actions buttons, based on the PlaybackState of the media session.
- * If present, these actions will be preferred in the UI over [actions]
+ * Semantic actions buttons, based on the PlaybackState of the media session. If present, these
+ * actions will be preferred in the UI over [actions]
*/
val semanticActions: MediaButton? = null,
- /**
- * Package name of the app that's posting the media.
- */
+ /** Package name of the app that's posting the media. */
val packageName: String,
- /**
- * Unique media session identifier.
- */
+ /** Unique media session identifier. */
val token: MediaSession.Token?,
- /**
- * Action to perform when the player is tapped.
- * This is unrelated to {@link #actions}.
- */
+ /** Action to perform when the player is tapped. This is unrelated to {@link #actions}. */
val clickIntent: PendingIntent?,
- /**
- * Where the media is playing: phone, headphones, ear buds, remote session.
- */
+ /** Where the media is playing: phone, headphones, ear buds, remote session. */
val device: MediaDeviceData?,
/**
- * When active, a player will be displayed on keyguard and quick-quick settings.
- * This is unrelated to the stream being playing or not, a player will not be active if
- * timed out, or in resumption mode.
+ * When active, a player will be displayed on keyguard and quick-quick settings. This is
+ * unrelated to the stream being playing or not, a player will not be active if timed out, or in
+ * resumption mode.
*/
var active: Boolean,
- /**
- * Action that should be performed to restart a non active session.
- */
+ /** Action that should be performed to restart a non active session. */
var resumeAction: Runnable?,
- /**
- * Playback location: one of PLAYBACK_LOCAL, PLAYBACK_CAST_LOCAL, or PLAYBACK_CAST_REMOTE
- */
+ /** Playback location: one of PLAYBACK_LOCAL, PLAYBACK_CAST_LOCAL, or PLAYBACK_CAST_REMOTE */
var playbackLocation: Int = PLAYBACK_LOCAL,
/**
* Indicates that this player is a resumption player (ie. It only shows a play actions which
@@ -102,29 +75,19 @@ data class MediaData(
val notificationKey: String? = null,
var hasCheckedForResume: Boolean = false,
- /**
- * If apps do not report PlaybackState, set as null to imply 'undetermined'
- */
+ /** If apps do not report PlaybackState, set as null to imply 'undetermined' */
val isPlaying: Boolean? = null,
- /**
- * Set from the notification and used as fallback when PlaybackState cannot be determined
- */
+ /** Set from the notification and used as fallback when PlaybackState cannot be determined */
val isClearable: Boolean = true,
- /**
- * Timestamp when this player was last active.
- */
+ /** Timestamp when this player was last active. */
var lastActive: Long = 0L,
- /**
- * Instance ID for logging purposes
- */
+ /** Instance ID for logging purposes */
val instanceId: InstanceId,
- /**
- * The UID of the app, used for logging
- */
+ /** The UID of the app, used for logging */
val appUid: Int
) {
companion object {
@@ -141,37 +104,21 @@ data class MediaData(
}
}
-/**
- * Contains [MediaAction] objects which represent specific buttons in the UI
- */
+/** Contains [MediaAction] objects which represent specific buttons in the UI */
data class MediaButton(
- /**
- * Play/pause button
- */
+ /** Play/pause button */
val playOrPause: MediaAction? = null,
- /**
- * Next button, or custom action
- */
+ /** Next button, or custom action */
val nextOrCustom: MediaAction? = null,
- /**
- * Previous button, or custom action
- */
+ /** Previous button, or custom action */
val prevOrCustom: MediaAction? = null,
- /**
- * First custom action space
- */
+ /** First custom action space */
val custom0: MediaAction? = null,
- /**
- * Second custom action space
- */
+ /** Second custom action space */
val custom1: MediaAction? = null,
- /**
- * Whether to reserve the empty space when the nextOrCustom is null
- */
+ /** Whether to reserve the empty space when the nextOrCustom is null */
val reserveNext: Boolean = false,
- /**
- * Whether to reserve the empty space when the prevOrCustom is null
- */
+ /** Whether to reserve the empty space when the prevOrCustom is null */
val reservePrev: Boolean = false
) {
fun getActionById(id: Int): MediaAction? {
@@ -201,7 +148,8 @@ data class MediaAction(
/** State of the media device. */
data class MediaDeviceData
-@JvmOverloads constructor(
+@JvmOverloads
+constructor(
/** Whether or not to enable the chip */
val enabled: Boolean,
@@ -221,8 +169,8 @@ data class MediaDeviceData
val showBroadcastButton: Boolean
) {
/**
- * Check whether [MediaDeviceData] objects are equal in all fields except the icon. The icon
- * is ignored because it can change by reference frequently depending on the device type's
+ * Check whether [MediaDeviceData] objects are equal in all fields except the icon. The icon is
+ * ignored because it can change by reference frequently depending on the device type's
* implementation, but this is not usually relevant unless other info has changed
*/
fun equalsWithoutIcon(other: MediaDeviceData?): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
index fc9515c050e5..2511324a943e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.player
import android.view.LayoutInflater
import android.view.View
@@ -25,13 +25,12 @@ import android.widget.SeekBar
import android.widget.TextView
import androidx.constraintlayout.widget.Barrier
import com.android.systemui.R
+import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.util.animation.TransitionLayout
private const val TAG = "MediaViewHolder"
-/**
- * Holder class for media player view
- */
+/** Holder class for media player view */
class MediaViewHolder constructor(itemView: View) {
val player = itemView as TransitionLayout
@@ -52,8 +51,7 @@ class MediaViewHolder constructor(itemView: View) {
// These views are only shown while the user is actively scrubbing
val scrubbingElapsedTimeView: TextView =
itemView.requireViewById(R.id.media_scrubbing_elapsed_time)
- val scrubbingTotalTimeView: TextView =
- itemView.requireViewById(R.id.media_scrubbing_total_time)
+ val scrubbingTotalTimeView: TextView = itemView.requireViewById(R.id.media_scrubbing_total_time)
val gutsViewHolder = GutsViewHolder(itemView)
@@ -86,15 +84,7 @@ class MediaViewHolder constructor(itemView: View) {
}
fun getTransparentActionButtons(): List<ImageButton> {
- return listOf(
- actionNext,
- actionPrev,
- action0,
- action1,
- action2,
- action3,
- action4
- )
+ return listOf(actionNext, actionPrev, action0, action1, action2, action3, action4)
}
fun marquee(start: Boolean, delay: Long) {
@@ -108,10 +98,8 @@ class MediaViewHolder constructor(itemView: View) {
* @param inflater LayoutInflater to use to inflate the layout.
* @param parent Parent of inflated view.
*/
- @JvmStatic fun create(
- inflater: LayoutInflater,
- parent: ViewGroup
- ): MediaViewHolder {
+ @JvmStatic
+ fun create(inflater: LayoutInflater, parent: ViewGroup): MediaViewHolder {
val mediaView = inflater.inflate(R.layout.media_session_view, parent, false)
mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
// Because this media view (a TransitionLayout) is used to measure and layout the views
@@ -124,7 +112,8 @@ class MediaViewHolder constructor(itemView: View) {
}
}
- val controlsIds = setOf(
+ val controlsIds =
+ setOf(
R.id.icon,
R.id.app_name,
R.id.header_title,
@@ -142,27 +131,23 @@ class MediaViewHolder constructor(itemView: View) {
R.id.icon,
R.id.media_scrubbing_elapsed_time,
R.id.media_scrubbing_total_time
- )
+ )
// Buttons used for notification-based actions
- val genericButtonIds = setOf(
- R.id.action0,
- R.id.action1,
- R.id.action2,
- R.id.action3,
- R.id.action4
- )
-
- val expandedBottomActionIds = setOf(
- R.id.actionPrev,
- R.id.actionNext,
- R.id.action0,
- R.id.action1,
- R.id.action2,
- R.id.action3,
- R.id.action4,
- R.id.media_scrubbing_elapsed_time,
- R.id.media_scrubbing_total_time
- )
+ val genericButtonIds =
+ setOf(R.id.action0, R.id.action1, R.id.action2, R.id.action3, R.id.action4)
+
+ val expandedBottomActionIds =
+ setOf(
+ R.id.actionPrev,
+ R.id.actionNext,
+ R.id.action0,
+ R.id.action1,
+ R.id.action2,
+ R.id.action3,
+ R.id.action4,
+ R.id.media_scrubbing_elapsed_time,
+ R.id.media_scrubbing_total_time
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index 121021f19f70..37d956bd09eb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.player
import android.animation.Animator
import android.animation.ObjectAnimator
@@ -24,40 +24,56 @@ import androidx.lifecycle.Observer
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
+import com.android.systemui.media.controls.ui.SquigglyProgress
/**
* Observer for changes from SeekBarViewModel.
*
* <p>Updates the seek bar views in response to changes to the model.
*/
-open class SeekBarObserver(
- private val holder: MediaViewHolder
-) : Observer<SeekBarViewModel.Progress> {
+open class SeekBarObserver(private val holder: MediaViewHolder) :
+ Observer<SeekBarViewModel.Progress> {
companion object {
@JvmStatic val RESET_ANIMATION_DURATION_MS: Int = 750
@JvmStatic val RESET_ANIMATION_THRESHOLD_MS: Int = 250
}
- val seekBarEnabledMaxHeight = holder.seekBar.context.resources
- .getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_height)
- val seekBarDisabledHeight = holder.seekBar.context.resources
- .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_height)
- val seekBarEnabledVerticalPadding = holder.seekBar.context.resources
- .getDimensionPixelSize(R.dimen.qs_media_session_enabled_seekbar_vertical_padding)
- val seekBarDisabledVerticalPadding = holder.seekBar.context.resources
- .getDimensionPixelSize(R.dimen.qs_media_session_disabled_seekbar_vertical_padding)
+ val seekBarEnabledMaxHeight =
+ holder.seekBar.context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_enabled_seekbar_height
+ )
+ val seekBarDisabledHeight =
+ holder.seekBar.context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_disabled_seekbar_height
+ )
+ val seekBarEnabledVerticalPadding =
+ holder.seekBar.context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_enabled_seekbar_vertical_padding
+ )
+ val seekBarDisabledVerticalPadding =
+ holder.seekBar.context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_disabled_seekbar_vertical_padding
+ )
var seekBarResetAnimator: Animator? = null
init {
- val seekBarProgressWavelength = holder.seekBar.context.resources
- .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_wavelength).toFloat()
- val seekBarProgressAmplitude = holder.seekBar.context.resources
- .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_amplitude).toFloat()
- val seekBarProgressPhase = holder.seekBar.context.resources
- .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_phase).toFloat()
- val seekBarProgressStrokeWidth = holder.seekBar.context.resources
- .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_stroke_width).toFloat()
+ val seekBarProgressWavelength =
+ holder.seekBar.context.resources
+ .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_wavelength)
+ .toFloat()
+ val seekBarProgressAmplitude =
+ holder.seekBar.context.resources
+ .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_amplitude)
+ .toFloat()
+ val seekBarProgressPhase =
+ holder.seekBar.context.resources
+ .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_phase)
+ .toFloat()
+ val seekBarProgressStrokeWidth =
+ holder.seekBar.context.resources
+ .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_stroke_width)
+ .toFloat()
val progressDrawable = holder.seekBar.progressDrawable as? SquigglyProgress
progressDrawable?.let {
it.waveLength = seekBarProgressWavelength
@@ -97,16 +113,18 @@ open class SeekBarObserver(
}
holder.seekBar.setMax(data.duration)
- val totalTimeString = DateUtils.formatElapsedTime(
- data.duration / DateUtils.SECOND_IN_MILLIS)
+ val totalTimeString =
+ DateUtils.formatElapsedTime(data.duration / DateUtils.SECOND_IN_MILLIS)
if (data.scrubbing) {
holder.scrubbingTotalTimeView.text = totalTimeString
}
data.elapsedTime?.let {
if (!data.scrubbing && !(seekBarResetAnimator?.isRunning ?: false)) {
- if (it <= RESET_ANIMATION_THRESHOLD_MS &&
- holder.seekBar.progress > RESET_ANIMATION_THRESHOLD_MS) {
+ if (
+ it <= RESET_ANIMATION_THRESHOLD_MS &&
+ holder.seekBar.progress > RESET_ANIMATION_THRESHOLD_MS
+ ) {
// This animation resets for every additional update to zero.
val animator = buildResetAnimator(it)
animator.start()
@@ -116,24 +134,29 @@ open class SeekBarObserver(
}
}
- val elapsedTimeString = DateUtils.formatElapsedTime(
- it / DateUtils.SECOND_IN_MILLIS)
+ val elapsedTimeString = DateUtils.formatElapsedTime(it / DateUtils.SECOND_IN_MILLIS)
if (data.scrubbing) {
holder.scrubbingElapsedTimeView.text = elapsedTimeString
}
- holder.seekBar.contentDescription = holder.seekBar.context.getString(
- R.string.controls_media_seekbar_description,
- elapsedTimeString,
- totalTimeString
- )
+ holder.seekBar.contentDescription =
+ holder.seekBar.context.getString(
+ R.string.controls_media_seekbar_description,
+ elapsedTimeString,
+ totalTimeString
+ )
}
}
@VisibleForTesting
open fun buildResetAnimator(targetTime: Int): Animator {
- val animator = ObjectAnimator.ofInt(holder.seekBar, "progress",
- holder.seekBar.progress, targetTime + RESET_ANIMATION_DURATION_MS)
+ val animator =
+ ObjectAnimator.ofInt(
+ holder.seekBar,
+ "progress",
+ holder.seekBar.progress,
+ targetTime + RESET_ANIMATION_DURATION_MS
+ )
animator.setAutoCancel(true)
animator.duration = RESET_ANIMATION_DURATION_MS.toLong()
animator.interpolator = Interpolators.EMPHASIZED
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index 17ebfecdd1fa..bba5f350dd16 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.player
import android.media.MediaMetadata
import android.media.session.MediaController
@@ -33,7 +33,6 @@ import androidx.lifecycle.MutableLiveData
import com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.util.concurrency.RepeatableExecutor
import javax.inject.Inject
@@ -43,8 +42,8 @@ private const val MIN_FLING_VELOCITY_SCALE_FACTOR = 10
private fun PlaybackState.isInMotion(): Boolean {
return this.state == PlaybackState.STATE_PLAYING ||
- this.state == PlaybackState.STATE_FAST_FORWARDING ||
- this.state == PlaybackState.STATE_REWINDING
+ this.state == PlaybackState.STATE_FAST_FORWARDING ||
+ this.state == PlaybackState.STATE_REWINDING
}
/**
@@ -60,8 +59,8 @@ private fun PlaybackState.computePosition(duration: Long): Long {
val updateTime = this.getLastPositionUpdateTime()
val currentTime = SystemClock.elapsedRealtime()
if (updateTime > 0) {
- var position = (this.playbackSpeed * (currentTime - updateTime)).toLong() +
- this.getPosition()
+ var position =
+ (this.playbackSpeed * (currentTime - updateTime)).toLong() + this.getPosition()
if (duration >= 0 && position > duration) {
position = duration.toLong()
} else if (position < 0) {
@@ -74,7 +73,9 @@ private fun PlaybackState.computePosition(duration: Long): Long {
}
/** ViewModel for seek bar in QS media player. */
-class SeekBarViewModel @Inject constructor(
+class SeekBarViewModel
+@Inject
+constructor(
@Background private val bgExecutor: RepeatableExecutor,
private val falsingManager: FalsingManager,
) {
@@ -87,9 +88,7 @@ class SeekBarViewModel @Inject constructor(
}
_progress.postValue(value)
}
- private val _progress = MutableLiveData<Progress>().apply {
- postValue(_data)
- }
+ private val _progress = MutableLiveData<Progress>().apply { postValue(_data) }
val progress: LiveData<Progress>
get() = _progress
private var controller: MediaController? = null
@@ -101,20 +100,21 @@ class SeekBarViewModel @Inject constructor(
}
}
private var playbackState: PlaybackState? = null
- private var callback = object : MediaController.Callback() {
- override fun onPlaybackStateChanged(state: PlaybackState?) {
- playbackState = state
- if (playbackState == null || PlaybackState.STATE_NONE.equals(playbackState)) {
- clearController()
- } else {
- checkIfPollingNeeded()
+ private var callback =
+ object : MediaController.Callback() {
+ override fun onPlaybackStateChanged(state: PlaybackState?) {
+ playbackState = state
+ if (playbackState == null || PlaybackState.STATE_NONE.equals(playbackState)) {
+ clearController()
+ } else {
+ checkIfPollingNeeded()
+ }
}
- }
- override fun onSessionDestroyed() {
- clearController()
+ override fun onSessionDestroyed() {
+ clearController()
+ }
}
- }
private var cancel: Runnable? = null
/** Indicates if the seek interaction is considered a false guesture. */
@@ -122,12 +122,13 @@ class SeekBarViewModel @Inject constructor(
/** Listening state (QS open or closed) is used to control polling of progress. */
var listening = true
- set(value) = bgExecutor.execute {
- if (field != value) {
- field = value
- checkIfPollingNeeded()
+ set(value) =
+ bgExecutor.execute {
+ if (field != value) {
+ field = value
+ checkIfPollingNeeded()
+ }
}
- }
private var scrubbingChangeListener: ScrubbingChangeListener? = null
private var enabledChangeListener: EnabledChangeListener? = null
@@ -145,14 +146,13 @@ class SeekBarViewModel @Inject constructor(
lateinit var logSeek: () -> Unit
- /**
- * Event indicating that the user has started interacting with the seek bar.
- */
+ /** Event indicating that the user has started interacting with the seek bar. */
@AnyThread
- fun onSeekStarting() = bgExecutor.execute {
- scrubbing = true
- isFalseSeek = false
- }
+ fun onSeekStarting() =
+ bgExecutor.execute {
+ scrubbing = true
+ isFalseSeek = false
+ }
/**
* Event indicating that the user has moved the seek bar.
@@ -160,47 +160,51 @@ class SeekBarViewModel @Inject constructor(
* @param position Current location in the track.
*/
@AnyThread
- fun onSeekProgress(position: Long) = bgExecutor.execute {
- if (scrubbing) {
- // The user hasn't yet finished their touch gesture, so only update the data for visual
- // feedback and don't update [controller] yet.
- _data = _data.copy(elapsedTime = position.toInt())
- } else {
- // The seek progress came from an a11y action and we should immediately update to the
- // new position. (a11y actions to change the seekbar position don't trigger
- // SeekBar.OnSeekBarChangeListener.onStartTrackingTouch or onStopTrackingTouch.)
- onSeek(position)
+ fun onSeekProgress(position: Long) =
+ bgExecutor.execute {
+ if (scrubbing) {
+ // The user hasn't yet finished their touch gesture, so only update the data for
+ // visual
+ // feedback and don't update [controller] yet.
+ _data = _data.copy(elapsedTime = position.toInt())
+ } else {
+ // The seek progress came from an a11y action and we should immediately update to
+ // the
+ // new position. (a11y actions to change the seekbar position don't trigger
+ // SeekBar.OnSeekBarChangeListener.onStartTrackingTouch or onStopTrackingTouch.)
+ onSeek(position)
+ }
}
- }
- /**
- * Event indicating that the seek interaction is a false gesture and it should be ignored.
- */
+ /** Event indicating that the seek interaction is a false gesture and it should be ignored. */
@AnyThread
- fun onSeekFalse() = bgExecutor.execute {
- if (scrubbing) {
- isFalseSeek = true
+ fun onSeekFalse() =
+ bgExecutor.execute {
+ if (scrubbing) {
+ isFalseSeek = true
+ }
}
- }
/**
* Handle request to change the current position in the media track.
* @param position Place to seek to in the track.
*/
@AnyThread
- fun onSeek(position: Long) = bgExecutor.execute {
- if (isFalseSeek) {
- scrubbing = false
- checkPlaybackPosition()
- } else {
- logSeek()
- controller?.transportControls?.seekTo(position)
- // Invalidate the cached playbackState to avoid the thumb jumping back to the previous
- // position.
- playbackState = null
- scrubbing = false
+ fun onSeek(position: Long) =
+ bgExecutor.execute {
+ if (isFalseSeek) {
+ scrubbing = false
+ checkPlaybackPosition()
+ } else {
+ logSeek()
+ controller?.transportControls?.seekTo(position)
+ // Invalidate the cached playbackState to avoid the thumb jumping back to the
+ // previous
+ // position.
+ playbackState = null
+ scrubbing = false
+ }
}
- }
/**
* Updates media information.
@@ -217,11 +221,18 @@ class SeekBarViewModel @Inject constructor(
val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L
val position = playbackState?.position?.toInt()
val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0
- val playing = NotificationMediaManager
- .isPlayingState(playbackState?.state ?: PlaybackState.STATE_NONE)
- val enabled = if (playbackState == null ||
- playbackState?.getState() == PlaybackState.STATE_NONE ||
- (duration <= 0)) false else true
+ val playing =
+ NotificationMediaManager.isPlayingState(
+ playbackState?.state ?: PlaybackState.STATE_NONE
+ )
+ val enabled =
+ if (
+ playbackState == null ||
+ playbackState?.getState() == PlaybackState.STATE_NONE ||
+ (duration <= 0)
+ )
+ false
+ else true
_data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration)
checkIfPollingNeeded()
}
@@ -232,26 +243,26 @@ class SeekBarViewModel @Inject constructor(
* This should be called when the media session behind the controller has been destroyed.
*/
@AnyThread
- fun clearController() = bgExecutor.execute {
- controller = null
- playbackState = null
- cancel?.run()
- cancel = null
- _data = _data.copy(enabled = false)
- }
+ fun clearController() =
+ bgExecutor.execute {
+ controller = null
+ playbackState = null
+ cancel?.run()
+ cancel = null
+ _data = _data.copy(enabled = false)
+ }
- /**
- * Call to clean up any resources.
- */
+ /** Call to clean up any resources. */
@AnyThread
- fun onDestroy() = bgExecutor.execute {
- controller = null
- playbackState = null
- cancel?.run()
- cancel = null
- scrubbingChangeListener = null
- enabledChangeListener = null
- }
+ fun onDestroy() =
+ bgExecutor.execute {
+ controller = null
+ playbackState = null
+ cancel?.run()
+ cancel = null
+ scrubbingChangeListener = null
+ enabledChangeListener = null
+ }
@WorkerThread
private fun checkPlaybackPosition() {
@@ -267,8 +278,12 @@ class SeekBarViewModel @Inject constructor(
val needed = listening && !scrubbing && playbackState?.isInMotion() ?: false
if (needed) {
if (cancel == null) {
- cancel = bgExecutor.executeRepeatedly(this::checkPlaybackPosition, 0L,
- POSITION_UPDATE_INTERVAL_MILLIS)
+ cancel =
+ bgExecutor.executeRepeatedly(
+ this::checkPlaybackPosition,
+ 0L,
+ POSITION_UPDATE_INTERVAL_MILLIS
+ )
}
} else {
cancel?.run()
@@ -333,11 +348,7 @@ class SeekBarViewModel @Inject constructor(
}
override fun onStopTrackingTouch(bar: SeekBar) {
- // in addition to the normal functionality of both functions.
- // isFalseTouch returns true if there is a real/false tap since it is not a move.
- // isFalseTap returns true if there is a real/false move since it is not a tap.
- if (falsingManager.isFalseTouch(MEDIA_SEEKBAR) &&
- falsingManager.isFalseTap(LOW_PENALTY)) {
+ if (falsingManager.isFalseTouch(MEDIA_SEEKBAR)) {
viewModel.onSeekFalse()
}
viewModel.onSeek(bar.progress.toLong())
@@ -358,9 +369,10 @@ class SeekBarViewModel @Inject constructor(
// Gesture detector helps decide which touch events to intercept.
private val detector = GestureDetectorCompat(bar.context, this)
// Velocity threshold used to decide when a fling is considered a false gesture.
- private val flingVelocity: Int = ViewConfiguration.get(bar.context).run {
- getScaledMinimumFlingVelocity() * MIN_FLING_VELOCITY_SCALE_FACTOR
- }
+ private val flingVelocity: Int =
+ ViewConfiguration.get(bar.context).run {
+ getScaledMinimumFlingVelocity() * MIN_FLING_VELOCITY_SCALE_FACTOR
+ }
// Indicates if the gesture should go to the seek bar or if it should be intercepted.
private var shouldGoToSeekBar = false
@@ -390,9 +402,9 @@ class SeekBarViewModel @Inject constructor(
/**
* Handle down events that press down on the thumb.
*
- * On the down action, determine a target box around the thumb to know when a scroll
- * gesture starts by clicking on the thumb. The target box will be used by subsequent
- * onScroll events.
+ * On the down action, determine a target box around the thumb to know when a scroll gesture
+ * starts by clicking on the thumb. The target box will be used by subsequent onScroll
+ * events.
*
* Returns true when the down event hits within the target box of the thumb.
*/
@@ -403,17 +415,19 @@ class SeekBarViewModel @Inject constructor(
// TODO: account for thumb offset
val progress = bar.getProgress()
val range = bar.max - bar.min
- val widthFraction = if (range > 0) {
- (progress - bar.min).toDouble() / range
- } else {
- 0.0
- }
+ val widthFraction =
+ if (range > 0) {
+ (progress - bar.min).toDouble() / range
+ } else {
+ 0.0
+ }
val availableWidth = bar.width - padL - padR
- val thumbX = if (bar.isLayoutRtl()) {
- padL + availableWidth * (1 - widthFraction)
- } else {
- padL + availableWidth * widthFraction
- }
+ val thumbX =
+ if (bar.isLayoutRtl()) {
+ padL + availableWidth * (1 - widthFraction)
+ } else {
+ padL + availableWidth * widthFraction
+ }
// Set the min, max boundaries of the thumb box.
// I'm cheating by using the height of the seek bar as the width of the box.
val halfHeight: Int = bar.height / 2
diff --git a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
index 8ae75fc34acb..1a10b18a5a69 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.recommendation
import android.view.LayoutInflater
import android.view.View
@@ -22,6 +22,8 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.android.systemui.R
+import com.android.systemui.media.controls.models.GutsViewHolder
+import com.android.systemui.media.controls.ui.IlluminationDrawable
import com.android.systemui.util.animation.TransitionLayout
private const val TAG = "RecommendationViewHolder"
@@ -33,26 +35,30 @@ class RecommendationViewHolder private constructor(itemView: View) {
// Recommendation screen
val cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
- val mediaCoverItems = listOf<ImageView>(
- itemView.requireViewById(R.id.media_cover1),
- itemView.requireViewById(R.id.media_cover2),
- itemView.requireViewById(R.id.media_cover3)
- )
- val mediaCoverContainers = listOf<ViewGroup>(
- itemView.requireViewById(R.id.media_cover1_container),
- itemView.requireViewById(R.id.media_cover2_container),
- itemView.requireViewById(R.id.media_cover3_container)
- )
- val mediaTitles: List<TextView> = listOf(
- itemView.requireViewById(R.id.media_title1),
- itemView.requireViewById(R.id.media_title2),
- itemView.requireViewById(R.id.media_title3)
- )
- val mediaSubtitles: List<TextView> = listOf(
- itemView.requireViewById(R.id.media_subtitle1),
- itemView.requireViewById(R.id.media_subtitle2),
- itemView.requireViewById(R.id.media_subtitle3)
- )
+ val mediaCoverItems =
+ listOf<ImageView>(
+ itemView.requireViewById(R.id.media_cover1),
+ itemView.requireViewById(R.id.media_cover2),
+ itemView.requireViewById(R.id.media_cover3)
+ )
+ val mediaCoverContainers =
+ listOf<ViewGroup>(
+ itemView.requireViewById(R.id.media_cover1_container),
+ itemView.requireViewById(R.id.media_cover2_container),
+ itemView.requireViewById(R.id.media_cover3_container)
+ )
+ val mediaTitles: List<TextView> =
+ listOf(
+ itemView.requireViewById(R.id.media_title1),
+ itemView.requireViewById(R.id.media_title2),
+ itemView.requireViewById(R.id.media_title3)
+ )
+ val mediaSubtitles: List<TextView> =
+ listOf(
+ itemView.requireViewById(R.id.media_subtitle1),
+ itemView.requireViewById(R.id.media_subtitle2),
+ itemView.requireViewById(R.id.media_subtitle3)
+ )
val gutsViewHolder = GutsViewHolder(itemView)
@@ -76,13 +82,14 @@ class RecommendationViewHolder private constructor(itemView: View) {
* @param inflater LayoutInflater to use to inflate the layout.
* @param parent Parent of inflated view.
*/
- @JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup):
- RecommendationViewHolder {
+ @JvmStatic
+ fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
val itemView =
inflater.inflate(
R.layout.media_smartspace_recommendations,
parent,
- false /* attachToRoot */)
+ false /* attachToRoot */
+ )
// Because this media view (a TransitionLayout) is used to measure and layout the views
// in various states before being attached to its parent, we can't depend on the default
// LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
@@ -91,35 +98,38 @@ class RecommendationViewHolder private constructor(itemView: View) {
}
// Res Ids for the control components on the recommendation view.
- val controlsIds = setOf(
- R.id.recommendation_card_icon,
- R.id.media_cover1,
- R.id.media_cover2,
- R.id.media_cover3,
- R.id.media_cover1_container,
- R.id.media_cover2_container,
- R.id.media_cover3_container,
- R.id.media_title1,
- R.id.media_title2,
- R.id.media_title3,
- R.id.media_subtitle1,
- R.id.media_subtitle2,
- R.id.media_subtitle3
- )
+ val controlsIds =
+ setOf(
+ R.id.recommendation_card_icon,
+ R.id.media_cover1,
+ R.id.media_cover2,
+ R.id.media_cover3,
+ R.id.media_cover1_container,
+ R.id.media_cover2_container,
+ R.id.media_cover3_container,
+ R.id.media_title1,
+ R.id.media_title2,
+ R.id.media_title3,
+ R.id.media_subtitle1,
+ R.id.media_subtitle2,
+ R.id.media_subtitle3
+ )
- val mediaTitlesAndSubtitlesIds = setOf(
- R.id.media_title1,
- R.id.media_title2,
- R.id.media_title3,
- R.id.media_subtitle1,
- R.id.media_subtitle2,
- R.id.media_subtitle3
- )
+ val mediaTitlesAndSubtitlesIds =
+ setOf(
+ R.id.media_title1,
+ R.id.media_title2,
+ R.id.media_title3,
+ R.id.media_subtitle1,
+ R.id.media_subtitle2,
+ R.id.media_subtitle3
+ )
- val mediaContainersIds = setOf(
- R.id.media_cover1_container,
- R.id.media_cover2_container,
- R.id.media_cover3_container
- )
+ val mediaContainersIds =
+ setOf(
+ R.id.media_cover1_container,
+ R.id.media_cover2_container,
+ R.id.media_cover3_container
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
index c8f17d93bcc8..1df42c641df6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.recommendation
import android.app.smartspace.SmartspaceAction
import android.content.Context
@@ -22,55 +22,41 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.text.TextUtils
import android.util.Log
+import androidx.annotation.VisibleForTesting
import com.android.internal.logging.InstanceId
-import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME
+
+@VisibleForTesting const val KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME"
/** State of a Smartspace media recommendations view. */
data class SmartspaceMediaData(
- /**
- * Unique id of a Smartspace media target.
- */
+ /** Unique id of a Smartspace media target. */
val targetId: String,
- /**
- * Indicates if the status is active.
- */
+ /** Indicates if the status is active. */
val isActive: Boolean,
- /**
- * Package name of the media recommendations' provider-app.
- */
+ /** Package name of the media recommendations' provider-app. */
val packageName: String,
- /**
- * Action to perform when the card is tapped. Also contains the target's extra info.
- */
+ /** Action to perform when the card is tapped. Also contains the target's extra info. */
val cardAction: SmartspaceAction?,
- /**
- * List of media recommendations.
- */
+ /** List of media recommendations. */
val recommendations: List<SmartspaceAction>,
- /**
- * Intent for the user's initiated dismissal.
- */
+ /** Intent for the user's initiated dismissal. */
val dismissIntent: Intent?,
- /**
- * The timestamp in milliseconds that headphone is connected.
- */
+ /** The timestamp in milliseconds that headphone is connected. */
val headphoneConnectionTimeMillis: Long,
- /**
- * Instance ID for [MediaUiEventLogger]
- */
+ /** Instance ID for [MediaUiEventLogger] */
val instanceId: InstanceId
) {
/**
* Indicates if all the data is valid.
*
* TODO(b/230333302): Make MediaControlPanel more flexible so that we can display fewer than
+ * ```
* [NUM_REQUIRED_RECOMMENDATIONS].
+ * ```
*/
fun isValid() = getValidRecommendations().size >= NUM_REQUIRED_RECOMMENDATIONS
- /**
- * Returns the list of [recommendations] that have valid data.
- */
+ /** Returns the list of [recommendations] that have valid data. */
fun getValidRecommendations() = recommendations.filter { it.icon != null }
/** Returns the upstream app name if available. */
@@ -89,9 +75,10 @@ data class SmartspaceMediaData(
Log.w(
TAG,
"Package $packageName does not have a main launcher activity. " +
- "Fallback to full app name")
+ "Fallback to full app name"
+ )
return try {
- val applicationInfo = packageManager.getApplicationInfo(packageName, /* flags= */ 0)
+ val applicationInfo = packageManager.getApplicationInfo(packageName, /* flags= */ 0)
packageManager.getApplicationLabel(applicationInfo)
} catch (e: PackageManager.NameNotFoundException) {
null
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
index 140a1fef93f7..a7ed69a9ab73 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.media
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.controls.models.recommendation
import android.app.smartspace.SmartspaceTarget
import android.util.Log
@@ -23,7 +39,7 @@ class SmartspaceMediaDataProvider @Inject constructor() : BcSmartspaceDataPlugin
smartspaceMediaTargetListeners.remove(smartspaceTargetListener)
}
- /** Updates Smartspace data and propagates it to any listeners. */
+ /** Updates Smartspace data and propagates it to any listeners. */
override fun onTargetsAvailable(targets: List<SmartspaceTarget>) {
// Filter out non-media targets.
val mediaTargets = mutableListOf<SmartspaceTarget>()
diff --git a/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
index 94a0835f22f8..ff763d81f950 100644
--- a/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
@@ -14,20 +14,18 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
import android.content.Context
-
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.media.InfoMediaManager
import com.android.settingslib.media.LocalMediaManager
-
import javax.inject.Inject
-/**
- * Factory to create [LocalMediaManager] objects.
- */
-class LocalMediaManagerFactory @Inject constructor(
+/** Factory to create [LocalMediaManager] objects. */
+class LocalMediaManagerFactory
+@Inject
+constructor(
private val context: Context,
private val localBluetoothManager: LocalBluetoothManager?
) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt
index 311973ad5af0..789ef407ea9d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt
@@ -14,15 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.player.MediaDeviceData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import javax.inject.Inject
-/**
- * Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events.
- */
-class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
- MediaDeviceManager.Listener {
+/** Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events. */
+class MediaDataCombineLatest @Inject constructor() :
+ MediaDataManager.Listener, MediaDeviceManager.Listener {
private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()
@@ -60,11 +61,7 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
listeners.toSet().forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
}
- override fun onMediaDeviceChanged(
- key: String,
- oldKey: String?,
- data: MediaDeviceData?
- ) {
+ override fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) {
if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
entries[key] = entries.remove(oldKey)?.first to data
update(key, oldKey)
@@ -83,9 +80,7 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
*/
fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
- /**
- * Remove a listener registered with addListener.
- */
+ /** Remove a listener registered with addListener. */
fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
private fun update(key: String, oldKey: String?) {
@@ -93,18 +88,14 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
if (entry != null && device != null) {
val data = entry.copy(device = device)
val listenersCopy = listeners.toSet()
- listenersCopy.forEach {
- it.onMediaDataLoaded(key, oldKey, data)
- }
+ listenersCopy.forEach { it.onMediaDataLoaded(key, oldKey, data) }
}
}
private fun remove(key: String) {
entries.remove(key)?.let {
val listenersCopy = listeners.toSet()
- listenersCopy.forEach {
- it.onMediaDataRemoved(key)
- }
+ listenersCopy.forEach { it.onMediaDataRemoved(key) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index e0c8d66cb6fd..45b319b274b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
import android.content.Context
import android.os.SystemProperties
@@ -23,6 +23,9 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.util.time.SystemClock
@@ -34,7 +37,8 @@ import kotlin.collections.LinkedHashMap
private const val TAG = "MediaDataFilter"
private const val DEBUG = true
-private const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = ("com.google" +
+private const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME =
+ ("com.google" +
".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity")
private const val RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY = "resumable_media_max_age_seconds"
@@ -43,8 +47,8 @@ private const val RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY = "resumable_media_max_age
* available within this time window, smartspace recommendations will be shown instead.
*/
@VisibleForTesting
-internal val SMARTSPACE_MAX_AGE = SystemProperties
- .getLong("debug.sysui.smartspace_max_age", TimeUnit.MINUTES.toMillis(30))
+internal val SMARTSPACE_MAX_AGE =
+ SystemProperties.getLong("debug.sysui.smartspace_max_age", TimeUnit.MINUTES.toMillis(30))
/**
* Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user
@@ -54,7 +58,9 @@ internal val SMARTSPACE_MAX_AGE = SystemProperties
* This is added at the end of the pipeline since we may still need to handle callbacks from
* background users (e.g. timeouts).
*/
-class MediaDataFilter @Inject constructor(
+class MediaDataFilter
+@Inject
+constructor(
private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
private val broadcastSender: BroadcastSender,
@@ -76,12 +82,13 @@ class MediaDataFilter @Inject constructor(
private var reactivatedKey: String? = null
init {
- userTracker = object : CurrentUserTracker(broadcastDispatcher) {
- override fun onUserSwitched(newUserId: Int) {
- // Post this so we can be sure lockscreenUserManager already got the broadcast
- executor.execute { handleUserSwitched(newUserId) }
+ userTracker =
+ object : CurrentUserTracker(broadcastDispatcher) {
+ override fun onUserSwitched(newUserId: Int) {
+ // Post this so we can be sure lockscreenUserManager already got the broadcast
+ executor.execute { handleUserSwitched(newUserId) }
+ }
}
- }
userTracker.startTracking()
}
@@ -108,9 +115,7 @@ class MediaDataFilter @Inject constructor(
userEntries.put(key, data)
// Notify listeners
- listeners.forEach {
- it.onMediaDataLoaded(key, oldKey, data)
- }
+ listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) }
}
override fun onSmartspaceMediaDataLoaded(
@@ -128,14 +133,11 @@ class MediaDataFilter @Inject constructor(
smartspaceMediaData = data
// Before forwarding the smartspace target, first check if we have recently inactive media
- val sorted = userEntries.toSortedMap(compareBy {
- userEntries.get(it)?.lastActive ?: -1
- })
+ val sorted = userEntries.toSortedMap(compareBy { userEntries.get(it)?.lastActive ?: -1 })
val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted)
var smartspaceMaxAgeMillis = SMARTSPACE_MAX_AGE
data.cardAction?.let {
- val smartspaceMaxAgeSeconds =
- it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0)
+ val smartspaceMaxAgeSeconds = it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0)
if (smartspaceMaxAgeSeconds > 0) {
smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds)
}
@@ -152,13 +154,21 @@ class MediaDataFilter @Inject constructor(
Log.d(TAG, "reactivating $lastActiveKey instead of smartspace")
reactivatedKey = lastActiveKey
val mediaData = sorted.get(lastActiveKey)!!.copy(active = true)
- logger.logRecommendationActivated(mediaData.appUid, mediaData.packageName,
- mediaData.instanceId)
+ logger.logRecommendationActivated(
+ mediaData.appUid,
+ mediaData.packageName,
+ mediaData.instanceId
+ )
listeners.forEach {
- it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData,
- receivedSmartspaceCardLatency =
+ it.onMediaDataLoaded(
+ lastActiveKey,
+ lastActiveKey,
+ mediaData,
+ receivedSmartspaceCardLatency =
(systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis)
- .toInt(), isSsReactivated = true)
+ .toInt(),
+ isSsReactivated = true
+ )
}
}
} else {
@@ -170,8 +180,10 @@ class MediaDataFilter @Inject constructor(
Log.d(TAG, "Invalid recommendation data. Skip showing the rec card")
return
}
- logger.logRecommendationAdded(smartspaceMediaData.packageName,
- smartspaceMediaData.instanceId)
+ logger.logRecommendationAdded(
+ smartspaceMediaData.packageName,
+ smartspaceMediaData.instanceId
+ )
listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
}
@@ -179,9 +191,7 @@ class MediaDataFilter @Inject constructor(
allEntries.remove(key)
userEntries.remove(key)?.let {
// Only notify listeners if something actually changed
- listeners.forEach {
- it.onMediaDataRemoved(key)
- }
+ listeners.forEach { it.onMediaDataRemoved(key) }
}
}
@@ -194,16 +204,17 @@ class MediaDataFilter @Inject constructor(
// Notify listeners to update with actual active value
userEntries.get(lastActiveKey)?.let { mediaData ->
listeners.forEach {
- it.onMediaDataLoaded(
- lastActiveKey, lastActiveKey, mediaData, immediately)
+ it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData, immediately)
}
}
}
if (smartspaceMediaData.isActive) {
- smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId)
+ smartspaceMediaData =
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = smartspaceMediaData.targetId,
+ instanceId = smartspaceMediaData.instanceId
+ )
}
listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
}
@@ -218,25 +229,19 @@ class MediaDataFilter @Inject constructor(
userEntries.clear()
keyCopy.forEach {
if (DEBUG) Log.d(TAG, "Removing $it after user change")
- listenersCopy.forEach { listener ->
- listener.onMediaDataRemoved(it)
- }
+ listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
}
allEntries.forEach { (key, data) ->
if (lockscreenUserManager.isCurrentProfile(data.userId)) {
if (DEBUG) Log.d(TAG, "Re-adding $key after user change")
userEntries.put(key, data)
- listenersCopy.forEach { listener ->
- listener.onMediaDataLoaded(key, null, data)
- }
+ listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) }
}
}
}
- /**
- * Invoked when the user has dismissed the media carousel
- */
+ /** Invoked when the user has dismissed the media carousel */
fun onSwipeToDismiss() {
if (DEBUG) Log.d(TAG, "Media carousel swiped away")
val mediaKeys = userEntries.keys.toSet()
@@ -247,55 +252,52 @@ class MediaDataFilter @Inject constructor(
if (smartspaceMediaData.isActive) {
val dismissIntent = smartspaceMediaData.dismissIntent
if (dismissIntent == null) {
- Log.w(TAG, "Cannot create dismiss action click action: " +
- "extras missing dismiss_intent.")
- } else if (dismissIntent.getComponent() != null &&
- dismissIntent.getComponent().getClassName()
- == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) {
+ Log.w(
+ TAG,
+ "Cannot create dismiss action click action: " + "extras missing dismiss_intent."
+ )
+ } else if (
+ dismissIntent.getComponent() != null &&
+ dismissIntent.getComponent().getClassName() ==
+ EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
+ ) {
// Dismiss the card Smartspace data through Smartspace trampoline activity.
context.startActivity(dismissIntent)
} else {
broadcastSender.sendBroadcast(dismissIntent)
}
- smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId)
- mediaDataManager.dismissSmartspaceRecommendation(smartspaceMediaData.targetId,
- delay = 0L)
+ smartspaceMediaData =
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = smartspaceMediaData.targetId,
+ instanceId = smartspaceMediaData.instanceId
+ )
+ mediaDataManager.dismissSmartspaceRecommendation(
+ smartspaceMediaData.targetId,
+ delay = 0L
+ )
}
}
- /**
- * Are there any active media entries, including the recommendation?
- */
- fun hasActiveMediaOrRecommendation() = userEntries.any { it.value.active } ||
+ /** Are there any active media entries, including the recommendation? */
+ fun hasActiveMediaOrRecommendation() =
+ userEntries.any { it.value.active } ||
(smartspaceMediaData.isActive &&
(smartspaceMediaData.isValid() || reactivatedKey != null))
- /**
- * Are there any media entries we should display?
- */
- fun hasAnyMediaOrRecommendation() = userEntries.isNotEmpty() ||
- (smartspaceMediaData.isActive && smartspaceMediaData.isValid())
+ /** Are there any media entries we should display? */
+ fun hasAnyMediaOrRecommendation() =
+ userEntries.isNotEmpty() || (smartspaceMediaData.isActive && smartspaceMediaData.isValid())
- /**
- * Are there any media notifications active (excluding the recommendation)?
- */
+ /** Are there any media notifications active (excluding the recommendation)? */
fun hasActiveMedia() = userEntries.any { it.value.active }
- /**
- * Are there any media entries we should display (excluding the recommendation)?
- */
+ /** Are there any media entries we should display (excluding the recommendation)? */
fun hasAnyMedia() = userEntries.isNotEmpty()
- /**
- * Add a listener for filtered [MediaData] changes
- */
+ /** Add a listener for filtered [MediaData] changes */
fun addListener(listener: MediaDataManager.Listener) = _listeners.add(listener)
- /**
- * Remove a listener that was registered with addListener
- */
+ /** Remove a listener that was registered with addListener */
fun removeListener(listener: MediaDataManager.Listener) = _listeners.remove(listener)
/**
@@ -315,8 +317,6 @@ class MediaDataFilter @Inject constructor(
val now = systemClock.elapsedRealtime()
val lastActiveKey = sortedEntries.lastKey() // most recently active
- return sortedEntries.get(lastActiveKey)?.let {
- now - it.lastActive
- } ?: Long.MAX_VALUE
+ return sortedEntries.get(lastActiveKey)?.let { now - it.lastActive } ?: Long.MAX_VALUE
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 5096a797a32b..14dd99023b92 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
import android.app.Notification
import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
@@ -57,6 +57,17 @@ 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.media.controls.models.player.MediaAction
+import com.android.systemui.media.controls.models.player.MediaButton
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.player.MediaDeviceData
+import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
+import com.android.systemui.media.controls.resume.MediaResumeListener
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
@@ -75,17 +86,19 @@ import java.util.concurrent.Executors
import javax.inject.Inject
// URI fields to try loading album art from
-private val ART_URIS = arrayOf(
+private val ART_URIS =
+ arrayOf(
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
MediaMetadata.METADATA_KEY_ART_URI,
MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
-)
+ )
private const val TAG = "MediaDataManager"
private const val DEBUG = true
private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent"
-private val LOADING = MediaData(
+private val LOADING =
+ MediaData(
userId = -1,
initialized = false,
app = null,
@@ -102,37 +115,41 @@ private val LOADING = MediaData(
active = true,
resumeAction = null,
instanceId = InstanceId.fakeInstanceId(-1),
- appUid = Process.INVALID_UID)
+ appUid = Process.INVALID_UID
+ )
@VisibleForTesting
-internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData(
- targetId = "INVALID",
- isActive = false,
- packageName = "INVALID",
- cardAction = null,
- recommendations = emptyList(),
- dismissIntent = null,
- headphoneConnectionTimeMillis = 0,
- instanceId = InstanceId.fakeInstanceId(-1))
+internal val EMPTY_SMARTSPACE_MEDIA_DATA =
+ SmartspaceMediaData(
+ targetId = "INVALID",
+ isActive = false,
+ packageName = "INVALID",
+ cardAction = null,
+ recommendations = emptyList(),
+ dismissIntent = null,
+ headphoneConnectionTimeMillis = 0,
+ instanceId = InstanceId.fakeInstanceId(-1)
+ )
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
return sbn.notification.isMediaNotification()
}
/**
- * Allow recommendations from smartspace to show in media controls.
- * Requires [Utils.useQsMediaPlayer] to be enabled.
- * On by default, but can be disabled by setting to 0
+ * Allow recommendations from smartspace to show in media controls. Requires
+ * [Utils.useQsMediaPlayer] to be enabled. On by default, but can be disabled by setting to 0
*/
private fun allowMediaRecommendations(context: Context): Boolean {
- val flag = Settings.Secure.getInt(context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1)
+ val flag =
+ Settings.Secure.getInt(
+ context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+ 1
+ )
return Utils.useQsMediaPlayer(context) && flag > 0
}
-/**
- * A class that facilitates management and loading of Media Data, ready for binding.
- */
+/** A class that facilitates management and loading of Media Data, ready for binding. */
@SysUISingleton
class MediaDataManager(
private val context: Context,
@@ -159,24 +176,24 @@ class MediaDataManager(
companion object {
// UI surface label for subscribing Smartspace updates.
- @JvmField
- val SMARTSPACE_UI_SURFACE_LABEL = "media_data_manager"
+ @JvmField val SMARTSPACE_UI_SURFACE_LABEL = "media_data_manager"
// Smartspace package name's extra key.
- @JvmField
- val EXTRAS_MEDIA_SOURCE_PACKAGE_NAME = "package_name"
+ @JvmField val EXTRAS_MEDIA_SOURCE_PACKAGE_NAME = "package_name"
// Maximum number of actions allowed in compact view
- @JvmField
- val MAX_COMPACT_ACTIONS = 3
+ @JvmField val MAX_COMPACT_ACTIONS = 3
// Maximum number of actions allowed in expanded view
- @JvmField
- val MAX_NOTIFICATION_ACTIONS = MediaViewHolder.genericButtonIds.size
+ @JvmField val MAX_NOTIFICATION_ACTIONS = MediaViewHolder.genericButtonIds.size
}
- private val themeText = com.android.settingslib.Utils.getColorAttr(context,
- com.android.internal.R.attr.textColorPrimary).defaultColor
+ private val themeText =
+ com.android.settingslib.Utils.getColorAttr(
+ context,
+ com.android.internal.R.attr.textColorPrimary
+ )
+ .defaultColor
// Internal listeners are part of the internal pipeline. External listeners (those registered
// with [MediaDeviceManager.addListener]) receive events after they have propagated through
@@ -192,9 +209,7 @@ class MediaDataManager(
private var smartspaceSession: SmartspaceSession? = null
private var allowMediaRecommendations = allowMediaRecommendations(context)
- /**
- * Check whether this notification is an RCN
- */
+ /** Check whether this notification is an RCN */
private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean {
return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
}
@@ -219,29 +234,44 @@ class MediaDataManager(
tunerService: TunerService,
mediaFlags: MediaFlags,
logger: MediaUiEventLogger
- ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
- broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
- mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
- activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context),
- Utils.useQsMediaPlayer(context), clock, tunerService, mediaFlags, logger)
-
- private val appChangeReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- when (intent.action) {
- Intent.ACTION_PACKAGES_SUSPENDED -> {
- val packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- packages?.forEach {
- removeAllForPackage(it)
+ ) : this(
+ context,
+ backgroundExecutor,
+ foregroundExecutor,
+ mediaControllerFactory,
+ broadcastDispatcher,
+ dumpManager,
+ mediaTimeoutListener,
+ mediaResumeListener,
+ mediaSessionBasedFilter,
+ mediaDeviceManager,
+ mediaDataCombineLatest,
+ mediaDataFilter,
+ activityStarter,
+ smartspaceMediaDataProvider,
+ Utils.useMediaResumption(context),
+ Utils.useQsMediaPlayer(context),
+ clock,
+ tunerService,
+ mediaFlags,
+ logger
+ )
+
+ private val appChangeReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ when (intent.action) {
+ Intent.ACTION_PACKAGES_SUSPENDED -> {
+ val packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ packages?.forEach { removeAllForPackage(it) }
}
- }
- Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_RESTARTED -> {
- intent.data?.encodedSchemeSpecificPart?.let {
- removeAllForPackage(it)
+ Intent.ACTION_PACKAGE_REMOVED,
+ Intent.ACTION_PACKAGE_RESTARTED -> {
+ intent.data?.encodedSchemeSpecificPart?.let { removeAllForPackage(it) }
}
}
}
}
- }
init {
dumpManager.registerDumpable(TAG, this)
@@ -262,20 +292,23 @@ class MediaDataManager(
// Set up links back into the pipeline for listeners that need to send events upstream.
mediaTimeoutListener.timeoutCallback = { key: String, timedOut: Boolean ->
- setTimedOut(key, timedOut) }
+ setTimedOut(key, timedOut)
+ }
mediaTimeoutListener.stateCallback = { key: String, state: PlaybackState ->
- updateState(key, state) }
+ updateState(key, state)
+ }
mediaResumeListener.setManager(this)
mediaDataFilter.mediaDataManager = this
val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED)
broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL)
- val uninstallFilter = IntentFilter().apply {
- addAction(Intent.ACTION_PACKAGE_REMOVED)
- addAction(Intent.ACTION_PACKAGE_RESTARTED)
- addDataScheme("package")
- }
+ val uninstallFilter =
+ IntentFilter().apply {
+ addAction(Intent.ACTION_PACKAGE_REMOVED)
+ addAction(Intent.ACTION_PACKAGE_RESTARTED)
+ addDataScheme("package")
+ }
// BroadcastDispatcher does not allow filters with data schemes
context.registerReceiver(appChangeReceiver, uninstallFilter)
@@ -283,8 +316,10 @@ class MediaDataManager(
smartspaceMediaDataProvider.registerListener(this)
val smartspaceManager: SmartspaceManager =
context.getSystemService(SmartspaceManager::class.java)
- smartspaceSession = smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build())
+ smartspaceSession =
+ smartspaceManager.createSmartspaceSession(
+ SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
+ )
smartspaceSession?.let {
it.addOnTargetsAvailableListener(
// Use a new thread listening to Smartspace updates instead of using the existing
@@ -296,17 +331,24 @@ class MediaDataManager(
Executors.newCachedThreadPool(),
SmartspaceSession.OnTargetsAvailableListener { targets ->
smartspaceMediaDataProvider.onTargetsAvailable(targets)
- })
+ }
+ )
}
smartspaceSession?.let { it.requestSmartspaceUpdate() }
- tunerService.addTunable(object : TunerService.Tunable {
- override fun onTuningChanged(key: String?, newValue: String?) {
- allowMediaRecommendations = allowMediaRecommendations(context)
- if (!allowMediaRecommendations) {
- dismissSmartspaceRecommendation(key = smartspaceMediaData.targetId, delay = 0L)
+ tunerService.addTunable(
+ object : TunerService.Tunable {
+ override fun onTuningChanged(key: String?, newValue: String?) {
+ allowMediaRecommendations = allowMediaRecommendations(context)
+ if (!allowMediaRecommendations) {
+ dismissSmartspaceRecommendation(
+ key = smartspaceMediaData.targetId,
+ delay = 0L
+ )
+ }
}
- }
- }, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)
+ },
+ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION
+ )
}
fun destroy() {
@@ -321,10 +363,7 @@ class MediaDataManager(
val oldKey = findExistingEntry(key, sbn.packageName)
if (oldKey == null) {
val instanceId = logger.getNewInstanceId()
- val temp = LOADING.copy(
- packageName = sbn.packageName,
- instanceId = instanceId
- )
+ val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
mediaEntries.put(key, temp)
logEvent = true
} else if (oldKey != key) {
@@ -342,9 +381,7 @@ class MediaDataManager(
private fun removeAllForPackage(packageName: String) {
Assert.isMainThread()
val toRemove = mediaEntries.filter { it.value.packageName == packageName }
- toRemove.forEach {
- removeEntry(it.key)
- }
+ toRemove.forEach { removeEntry(it.key) }
}
fun setResumeAction(key: String, action: Runnable?) {
@@ -366,32 +403,41 @@ class MediaDataManager(
// Resume controls don't have a notification key, so store by package name instead
if (!mediaEntries.containsKey(packageName)) {
val instanceId = logger.getNewInstanceId()
- val appUid = try {
- context.packageManager.getApplicationInfo(packageName, 0)?.uid!!
- } catch (e: PackageManager.NameNotFoundException) {
- Log.w(TAG, "Could not get app UID for $packageName", e)
- Process.INVALID_UID
- }
+ val appUid =
+ try {
+ context.packageManager.getApplicationInfo(packageName, 0)?.uid!!
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Could not get app UID for $packageName", e)
+ Process.INVALID_UID
+ }
- val resumeData = LOADING.copy(
- packageName = packageName,
- resumeAction = action,
- hasCheckedForResume = true,
- instanceId = instanceId,
- appUid = appUid
- )
+ val resumeData =
+ LOADING.copy(
+ packageName = packageName,
+ resumeAction = action,
+ hasCheckedForResume = true,
+ instanceId = instanceId,
+ appUid = appUid
+ )
mediaEntries.put(packageName, resumeData)
logger.logResumeMediaAdded(appUid, packageName, instanceId)
}
backgroundExecutor.execute {
- loadMediaDataInBgForResumption(userId, desc, action, token, appName, appIntent,
- packageName)
+ loadMediaDataInBgForResumption(
+ userId,
+ desc,
+ action,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
}
}
/**
- * Check if there is an existing entry that matches the key or package name.
- * Returns the key that matches, or null if not found.
+ * Check if there is an existing entry that matches the key or package name. Returns the key
+ * that matches, or null if not found.
*/
private fun findExistingEntry(key: String, packageName: String): String? {
if (mediaEntries.containsKey(key)) {
@@ -410,32 +456,24 @@ class MediaDataManager(
oldKey: String?,
logEvent: Boolean = false
) {
- backgroundExecutor.execute {
- loadMediaDataInBg(key, sbn, oldKey, logEvent)
- }
+ backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, logEvent) }
}
- /**
- * Add a listener for changes in this class
- */
+ /** Add a listener for changes in this class */
fun addListener(listener: Listener) {
// mediaDataFilter is the current end of the internal pipeline. Register external
// listeners as listeners to it.
mediaDataFilter.addListener(listener)
}
- /**
- * Remove a listener for changes in this class
- */
+ /** Remove a listener for changes in this class */
fun removeListener(listener: Listener) {
// Since mediaDataFilter is the current end of the internal pipelie, external listeners
// have been registered to it. So, they need to be removed from it too.
mediaDataFilter.removeListener(listener)
}
- /**
- * Add a listener for internal events.
- */
+ /** Add a listener for internal events. */
private fun addInternalListener(listener: Listener) = internalListeners.add(listener)
/**
@@ -483,8 +521,8 @@ class MediaDataManager(
}
/**
- * Called whenever the player has been paused or stopped for a while, or swiped from QQS.
- * This will make the player not active anymore, hiding it from QQS and Keyguard.
+ * Called whenever the player has been paused or stopped for a while, or swiped from QQS. This
+ * will make the player not active anymore, hiding it from QQS and Keyguard.
* @see MediaData.active
*/
internal fun setTimedOut(key: String, timedOut: Boolean, forceUpdate: Boolean = false) {
@@ -506,9 +544,7 @@ class MediaDataManager(
}
}
- /**
- * Called when the player's [PlaybackState] has been updated with new actions and/or state
- */
+ /** Called when the player's [PlaybackState] has been updated with new actions and/or state */
private fun updateState(key: String, state: PlaybackState) {
mediaEntries.get(key)?.let {
val token = it.token
@@ -516,22 +552,23 @@ class MediaDataManager(
if (DEBUG) Log.d(TAG, "State updated, but token was null")
return
}
- val actions = createActionsFromState(it.packageName,
- mediaControllerFactory.create(it.token), UserHandle(it.userId))
+ val actions =
+ createActionsFromState(
+ it.packageName,
+ mediaControllerFactory.create(it.token),
+ UserHandle(it.userId)
+ )
// Control buttons
// If flag is enabled and controller has a PlaybackState,
// create actions from session info
// otherwise, no need to update semantic actions.
- val data = if (actions != null) {
- it.copy(
- semanticActions = actions,
- isPlaying = isPlayingState(state.state))
- } else {
- it.copy(
- isPlaying = isPlayingState(state.state)
- )
- }
+ val data =
+ if (actions != null) {
+ it.copy(semanticActions = actions, isPlaying = isPlayingState(state.state))
+ } else {
+ it.copy(isPlaying = isPlayingState(state.state))
+ }
if (DEBUG) Log.d(TAG, "State updated outside of notification")
onMediaDataLoaded(key, key, data)
}
@@ -544,9 +581,7 @@ class MediaDataManager(
notifyMediaDataRemoved(key)
}
- /**
- * Dismiss a media entry. Returns false if the key was not found.
- */
+ /** Dismiss a media entry. Returns false if the key was not found. */
fun dismissMediaData(key: String, delay: Long): Boolean {
val existed = mediaEntries[key] != null
backgroundExecutor.execute {
@@ -564,9 +599,8 @@ class MediaDataManager(
}
/**
- * Called whenever the recommendation has been expired, or swiped from QQS.
- * This will make the recommendation view to not be shown anymore during this headphone
- * connection session.
+ * Called whenever the recommendation has been expired, or swiped from QQS. This will make the
+ * recommendation view to not be shown anymore during this headphone connection session.
*/
fun dismissSmartspaceRecommendation(key: String, delay: Long) {
if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid()) {
@@ -576,13 +610,16 @@ class MediaDataManager(
if (DEBUG) Log.d(TAG, "Dismissing Smartspace media target")
if (smartspaceMediaData.isActive) {
- smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId)
+ smartspaceMediaData =
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = smartspaceMediaData.targetId,
+ instanceId = smartspaceMediaData.instanceId
+ )
}
foregroundExecutor.executeDelayed(
- { notifySmartspaceMediaDataRemoved(
- smartspaceMediaData.targetId, immediately = true) }, delay)
+ { notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = true) },
+ delay
+ )
}
private fun loadMediaDataInBgForResumption(
@@ -610,11 +647,12 @@ class MediaDataManager(
if (artworkBitmap == null && desc.iconUri != null) {
artworkBitmap = loadBitmapFromUri(desc.iconUri!!)
}
- val artworkIcon = if (artworkBitmap != null) {
- Icon.createWithBitmap(artworkBitmap)
- } else {
- null
- }
+ val artworkIcon =
+ if (artworkBitmap != null) {
+ Icon.createWithBitmap(artworkBitmap)
+ } else {
+ null
+ }
val currentEntry = mediaEntries.get(packageName)
val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
@@ -623,13 +661,34 @@ class MediaDataManager(
val mediaAction = getResumeMediaAction(resumeAction)
val lastActive = systemClock.elapsedRealtime()
foregroundExecutor.execute {
- onMediaDataLoaded(packageName, null, MediaData(userId, true, appName,
- null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
- MediaButton(playOrPause = mediaAction), packageName, token, appIntent,
- device = null, active = false,
- resumeAction = resumeAction, resumption = true, notificationKey = packageName,
- hasCheckedForResume = true, lastActive = lastActive, instanceId = instanceId,
- appUid = appUid))
+ onMediaDataLoaded(
+ packageName,
+ null,
+ MediaData(
+ userId,
+ true,
+ appName,
+ null,
+ desc.subtitle,
+ desc.title,
+ artworkIcon,
+ listOf(mediaAction),
+ listOf(0),
+ MediaButton(playOrPause = mediaAction),
+ packageName,
+ token,
+ appIntent,
+ device = null,
+ active = false,
+ resumeAction = resumeAction,
+ resumption = true,
+ notificationKey = packageName,
+ hasCheckedForResume = true,
+ lastActive = lastActive,
+ instanceId = instanceId,
+ appUid = appUid
+ )
+ )
}
}
@@ -639,8 +698,11 @@ class MediaDataManager(
oldKey: String?,
logEvent: Boolean = false
) {
- val token = sbn.notification.extras.getParcelable(
- Notification.EXTRA_MEDIA_SESSION, MediaSession.Token::class.java)
+ val token =
+ sbn.notification.extras.getParcelable(
+ Notification.EXTRA_MEDIA_SESSION,
+ MediaSession.Token::class.java
+ )
if (token == null) {
return
}
@@ -648,10 +710,12 @@ class MediaDataManager(
val metadata = mediaController.metadata
val notif: Notification = sbn.notification
- val appInfo = notif.extras.getParcelable(
- Notification.EXTRA_BUILDER_APPLICATION_INFO,
- ApplicationInfo::class.java
- ) ?: getAppInfoFromPackage(sbn.packageName)
+ val appInfo =
+ notif.extras.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo::class.java
+ )
+ ?: getAppInfoFromPackage(sbn.packageName)
// Album art
var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
@@ -661,11 +725,12 @@ class MediaDataManager(
if (artworkBitmap == null) {
artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
}
- val artWorkIcon = if (artworkBitmap == null) {
- notif.getLargeIcon()
- } else {
- Icon.createWithBitmap(artworkBitmap)
- }
+ val artWorkIcon =
+ if (artworkBitmap == null) {
+ notif.getLargeIcon()
+ } else {
+ Icon.createWithBitmap(artworkBitmap)
+ }
// App name
val appName = getAppName(sbn, appInfo)
@@ -694,17 +759,27 @@ class MediaDataManager(
val extras = sbn.notification.extras
val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null)
val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1)
- val deviceIntent = extras.getParcelable(
- Notification.EXTRA_MEDIA_REMOTE_INTENT, PendingIntent::class.java)
+ val deviceIntent =
+ extras.getParcelable(
+ Notification.EXTRA_MEDIA_REMOTE_INTENT,
+ PendingIntent::class.java
+ )
Log.d(TAG, "$key is RCN for $deviceName")
if (deviceName != null && deviceIcon > -1) {
// Name and icon must be present, but intent may be null
val enabled = deviceIntent != null && deviceIntent.isActivity
- val deviceDrawable = Icon.createWithResource(sbn.packageName, deviceIcon)
+ val deviceDrawable =
+ Icon.createWithResource(sbn.packageName, deviceIcon)
.loadDrawable(sbn.getPackageContext(context))
- device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent,
- showBroadcastButton = false)
+ device =
+ MediaDeviceData(
+ enabled,
+ deviceDrawable,
+ deviceName,
+ deviceIntent,
+ showBroadcastButton = false
+ )
}
}
@@ -721,10 +796,13 @@ class MediaDataManager(
}
val playbackLocation =
- if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE
- else if (mediaController.playbackInfo?.playbackType ==
- MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL
- else MediaData.PLAYBACK_CAST_LOCAL
+ if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE
+ else if (
+ mediaController.playbackInfo?.playbackType ==
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL
+ )
+ MediaData.PLAYBACK_LOCAL
+ else MediaData.PLAYBACK_CAST_LOCAL
val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
val currentEntry = mediaEntries.get(key)
@@ -742,13 +820,36 @@ class MediaDataManager(
val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
val active = mediaEntries[key]?.active ?: true
- onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, appName,
- smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed,
- semanticActions, sbn.packageName, token, notif.contentIntent, device,
- active, resumeAction = resumeAction, playbackLocation = playbackLocation,
- notificationKey = key, hasCheckedForResume = hasCheckedForResume,
- isPlaying = isPlaying, isClearable = sbn.isClearable(),
- lastActive = lastActive, instanceId = instanceId, appUid = appUid))
+ onMediaDataLoaded(
+ key,
+ oldKey,
+ MediaData(
+ sbn.normalizedUserId,
+ true,
+ appName,
+ smallIcon,
+ artist,
+ song,
+ artWorkIcon,
+ actionIcons,
+ actionsToShowCollapsed,
+ semanticActions,
+ sbn.packageName,
+ token,
+ notif.contentIntent,
+ device,
+ active,
+ resumeAction = resumeAction,
+ playbackLocation = playbackLocation,
+ notificationKey = key,
+ hasCheckedForResume = hasCheckedForResume,
+ isPlaying = isPlaying,
+ isClearable = sbn.isClearable(),
+ lastActive = lastActive,
+ instanceId = instanceId,
+ appUid = appUid
+ )
+ )
}
}
@@ -774,27 +875,33 @@ class MediaDataManager(
}
}
- /**
- * Generate action buttons based on notification actions
- */
- private fun createActionsFromNotification(sbn: StatusBarNotification):
- Pair<List<MediaAction>, List<Int>> {
+ /** Generate action buttons based on notification actions */
+ private fun createActionsFromNotification(
+ sbn: StatusBarNotification
+ ): Pair<List<MediaAction>, List<Int>> {
val notif = sbn.notification
val actionIcons: MutableList<MediaAction> = ArrayList()
val actions = notif.actions
- var actionsToShowCollapsed = notif.extras.getIntArray(
- Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf()
+ var actionsToShowCollapsed =
+ notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList()
+ ?: mutableListOf()
if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
- Log.e(TAG, "Too many compact actions for ${sbn.key}," +
- "limiting to first $MAX_COMPACT_ACTIONS")
+ Log.e(
+ TAG,
+ "Too many compact actions for ${sbn.key}," +
+ "limiting to first $MAX_COMPACT_ACTIONS"
+ )
actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
}
if (actions != null) {
for ((index, action) in actions.withIndex()) {
if (index == MAX_NOTIFICATION_ACTIONS) {
- Log.w(TAG, "Too many notification actions for ${sbn.key}," +
- " limiting to first $MAX_NOTIFICATION_ACTIONS")
+ Log.w(
+ TAG,
+ "Too many notification actions for ${sbn.key}," +
+ " limiting to first $MAX_NOTIFICATION_ACTIONS"
+ )
break
}
if (action.getIcon() == null) {
@@ -802,33 +909,38 @@ class MediaDataManager(
actionsToShowCollapsed.remove(index)
continue
}
- val runnable = if (action.actionIntent != null) {
- Runnable {
- if (action.actionIntent.isActivity) {
- activityStarter.startPendingIntentDismissingKeyguard(
- action.actionIntent)
- } else if (action.isAuthenticationRequired()) {
- activityStarter.dismissKeyguardThenExecute({
- var result = sendPendingIntent(action.actionIntent)
- result
- }, {}, true)
- } else {
- sendPendingIntent(action.actionIntent)
+ val runnable =
+ if (action.actionIntent != null) {
+ Runnable {
+ if (action.actionIntent.isActivity) {
+ activityStarter.startPendingIntentDismissingKeyguard(
+ action.actionIntent
+ )
+ } else if (action.isAuthenticationRequired()) {
+ activityStarter.dismissKeyguardThenExecute(
+ {
+ var result = sendPendingIntent(action.actionIntent)
+ result
+ },
+ {},
+ true
+ )
+ } else {
+ sendPendingIntent(action.actionIntent)
+ }
}
+ } else {
+ null
}
- } else {
- null
- }
- val mediaActionIcon = if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
- Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
- } else {
- action.getIcon()
- }.setTint(themeText).loadDrawable(context)
- val mediaAction = MediaAction(
- mediaActionIcon,
- runnable,
- action.title,
- null)
+ val mediaActionIcon =
+ if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
+ Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
+ } else {
+ action.getIcon()
+ }
+ .setTint(themeText)
+ .loadDrawable(context)
+ val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null)
actionIcons.add(mediaAction)
}
}
@@ -841,7 +953,9 @@ class MediaDataManager(
* @param packageName Package name for the media app
* @param controller MediaController for the current session
* @return a Pair consisting of a list of media actions, and a list of ints representing which
+ * ```
* of those actions should be shown in the compact player
+ * ```
*/
private fun createActionsFromState(
packageName: String,
@@ -854,59 +968,69 @@ class MediaDataManager(
}
// First, check for standard actions
- val playOrPause = if (isConnectingState(state.state)) {
- // Spinner needs to be animating to render anything. Start it here.
- val drawable = context.getDrawable(
- com.android.internal.R.drawable.progress_small_material)
- (drawable as Animatable).start()
- MediaAction(
- drawable,
- null, // no action to perform when clicked
- context.getString(R.string.controls_media_button_connecting),
- context.getDrawable(R.drawable.ic_media_connecting_container),
- // Specify a rebind id to prevent the spinner from restarting on later binds.
- com.android.internal.R.drawable.progress_small_material
- )
- } else if (isPlayingState(state.state)) {
- getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
- } else {
- getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
- }
- val prevButton = getStandardAction(controller, state.actions,
- PlaybackState.ACTION_SKIP_TO_PREVIOUS)
- val nextButton = getStandardAction(controller, state.actions,
- PlaybackState.ACTION_SKIP_TO_NEXT)
+ val playOrPause =
+ if (isConnectingState(state.state)) {
+ // Spinner needs to be animating to render anything. Start it here.
+ val drawable =
+ context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+ (drawable as Animatable).start()
+ MediaAction(
+ drawable,
+ null, // no action to perform when clicked
+ context.getString(R.string.controls_media_button_connecting),
+ context.getDrawable(R.drawable.ic_media_connecting_container),
+ // Specify a rebind id to prevent the spinner from restarting on later binds.
+ com.android.internal.R.drawable.progress_small_material
+ )
+ } else if (isPlayingState(state.state)) {
+ getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
+ } else {
+ getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
+ }
+ val prevButton =
+ getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
+ val nextButton =
+ getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT)
// Then, create a way to build any custom actions that will be needed
- val customActions = state.customActions.asSequence().filterNotNull().map {
- getCustomAction(state, packageName, controller, it)
- }.iterator()
+ val customActions =
+ state.customActions
+ .asSequence()
+ .filterNotNull()
+ .map { getCustomAction(state, packageName, controller, it) }
+ .iterator()
fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
// Finally, assign the remaining button slots: play/pause A B C D
// A = previous, else custom action (if not reserved)
// B = next, else custom action (if not reserved)
// C and D are always custom actions
- val reservePrev = controller.extras?.getBoolean(
- MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV) == true
- val reserveNext = controller.extras?.getBoolean(
- MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT) == true
-
- val prevOrCustom = if (prevButton != null) {
- prevButton
- } else if (!reservePrev) {
- nextCustomAction()
- } else {
- null
- }
+ val reservePrev =
+ controller.extras?.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
+ ) == true
+ val reserveNext =
+ controller.extras?.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
+ ) == true
+
+ val prevOrCustom =
+ if (prevButton != null) {
+ prevButton
+ } else if (!reservePrev) {
+ nextCustomAction()
+ } else {
+ null
+ }
- val nextOrCustom = if (nextButton != null) {
- nextButton
- } else if (!reserveNext) {
- nextCustomAction()
- } else {
- null
- }
+ val nextOrCustom =
+ if (nextButton != null) {
+ nextButton
+ } else if (!reserveNext) {
+ nextCustomAction()
+ } else {
+ null
+ }
return MediaButton(
playOrPause,
@@ -925,11 +1049,14 @@ class MediaDataManager(
* @param controller MediaController for the session
* @param stateActions The actions included with the session's [PlaybackState]
* @param action A [PlaybackState.Actions] value representing what action to generate. One of:
+ * ```
* [PlaybackState.ACTION_PLAY]
* [PlaybackState.ACTION_PAUSE]
* [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
* [PlaybackState.ACTION_SKIP_TO_NEXT]
- * @return A [MediaAction] with correct values set, or null if the state doesn't support it
+ * @return
+ * ```
+ * A [MediaAction] with correct values set, or null if the state doesn't support it
*/
private fun getStandardAction(
controller: MediaController,
@@ -977,20 +1104,18 @@ class MediaDataManager(
}
}
- /**
- * Check whether the actions from a [PlaybackState] include a specific action
- */
+ /** Check whether the actions from a [PlaybackState] include a specific action */
private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
- if ((action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
- (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)) {
+ if (
+ (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
+ (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)
+ ) {
return true
}
return (stateActions and action != 0L)
}
- /**
- * Get a [MediaAction] representing a [PlaybackState.CustomAction]
- */
+ /** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
private fun getCustomAction(
state: PlaybackState,
packageName: String,
@@ -1005,9 +1130,7 @@ class MediaDataManager(
)
}
- /**
- * Load a bitmap from the various Art metadata URIs
- */
+ /** Load a bitmap from the various Art metadata URIs */
private fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
for (uri in ART_URIS) {
val uriString = metadata.getString(uri)
@@ -1042,16 +1165,18 @@ class MediaDataManager(
return null
}
- if (!uri.scheme.equals(ContentResolver.SCHEME_CONTENT) &&
+ if (
+ !uri.scheme.equals(ContentResolver.SCHEME_CONTENT) &&
!uri.scheme.equals(ContentResolver.SCHEME_ANDROID_RESOURCE) &&
- !uri.scheme.equals(ContentResolver.SCHEME_FILE)) {
+ !uri.scheme.equals(ContentResolver.SCHEME_FILE)
+ ) {
return null
}
val source = ImageDecoder.createSource(context.getContentResolver(), uri)
return try {
- ImageDecoder.decodeBitmap(source) {
- decoder, info, source -> decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
+ decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
}
} catch (e: IOException) {
Log.e(TAG, "Unable to load bitmap", e)
@@ -1065,25 +1190,23 @@ class MediaDataManager(
private fun getResumeMediaAction(action: Runnable): MediaAction {
return MediaAction(
Icon.createWithResource(context, R.drawable.ic_media_play)
- .setTint(themeText).loadDrawable(context),
+ .setTint(themeText)
+ .loadDrawable(context),
action,
context.getString(R.string.controls_media_resume),
context.getDrawable(R.drawable.ic_media_play_container)
)
}
- fun onMediaDataLoaded(
- key: String,
- oldKey: String?,
- data: MediaData
- ) = traceSection("MediaDataManager#onMediaDataLoaded") {
- Assert.isMainThread()
- if (mediaEntries.containsKey(key)) {
- // Otherwise this was removed already
- mediaEntries.put(key, data)
- notifyMediaDataLoaded(key, oldKey, data)
+ fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) =
+ traceSection("MediaDataManager#onMediaDataLoaded") {
+ Assert.isMainThread()
+ if (mediaEntries.containsKey(key)) {
+ // Otherwise this was removed already
+ mediaEntries.put(key, data)
+ notifyMediaDataLoaded(key, oldKey, data)
+ }
}
- }
override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) {
if (!allowMediaRecommendations) {
@@ -1100,9 +1223,11 @@ class MediaDataManager(
if (DEBUG) {
Log.d(TAG, "Set Smartspace media to be inactive for the data update")
}
- smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId)
+ smartspaceMediaData =
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = smartspaceMediaData.targetId,
+ instanceId = smartspaceMediaData.instanceId
+ )
notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
}
1 -> {
@@ -1113,15 +1238,16 @@ class MediaDataManager(
}
if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
smartspaceMediaData = toSmartspaceMediaData(newMediaTarget, isActive = true)
- notifySmartspaceMediaDataLoaded(
- smartspaceMediaData.targetId, smartspaceMediaData)
+ notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
}
else -> {
// There should NOT be more than 1 Smartspace media update. When it happens, it
// indicates a bad state or an error. Reset the status accordingly.
Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
notifySmartspaceMediaDataRemoved(
- smartspaceMediaData.targetId, false /* immediately */)
+ smartspaceMediaData.targetId,
+ false /* immediately */
+ )
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
}
}
@@ -1130,14 +1256,21 @@ class MediaDataManager(
fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaEntries.remove(key)
- if (useMediaResumption && removed?.resumeAction != null && removed?.isLocalSession()) {
+ if (useMediaResumption && removed?.resumeAction != null && removed.isLocalSession()) {
Log.d(TAG, "Not removing $key because resumable")
// Move to resume key (aka package name) if that key doesn't already exist.
val resumeAction = getResumeMediaAction(removed.resumeAction!!)
- val updated = removed.copy(token = null, actions = listOf(resumeAction),
+ val updated =
+ removed.copy(
+ token = null,
+ actions = listOf(resumeAction),
semanticActions = MediaButton(playOrPause = resumeAction),
- actionsToShowInCompact = listOf(0), active = false, resumption = true,
- isPlaying = false, isClearable = true)
+ actionsToShowInCompact = listOf(0),
+ active = false,
+ resumption = true,
+ isPlaying = false,
+ isClearable = true
+ )
val pkg = removed.packageName
val migrate = mediaEntries.put(pkg, updated) == null
// Notify listeners of "new" controls when migrating or removed and update when not
@@ -1179,33 +1312,27 @@ class MediaDataManager(
}
}
- /**
- * Invoked when the user has dismissed the media carousel
- */
+ /** Invoked when the user has dismissed the media carousel */
fun onSwipeToDismiss() = mediaDataFilter.onSwipeToDismiss()
- /**
- * Are there any media notifications active, including the recommendations?
- */
+ /** Are there any media notifications active, including the recommendations? */
fun hasActiveMediaOrRecommendation() = mediaDataFilter.hasActiveMediaOrRecommendation()
/**
* Are there any media entries we should display, including the recommendations?
- * If resumption is enabled, this will include inactive players
- * If resumption is disabled, we only want to show active players
+ * - If resumption is enabled, this will include inactive players
+ * - If resumption is disabled, we only want to show active players
*/
fun hasAnyMediaOrRecommendation() = mediaDataFilter.hasAnyMediaOrRecommendation()
- /**
- * Are there any resume media notifications active, excluding the recommendations?
- */
+ /** Are there any resume media notifications active, excluding the recommendations? */
fun hasActiveMedia() = mediaDataFilter.hasActiveMedia()
/**
- * Are there any resume media notifications active, excluding the recommendations?
- * If resumption is enabled, this will include inactive players
- * If resumption is disabled, we only want to show active players
- */
+ * Are there any resume media notifications active, excluding the recommendations?
+ * - If resumption is enabled, this will include inactive players
+ * - If resumption is disabled, we only want to show active players
+ */
fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()
interface Listener {
@@ -1275,10 +1402,9 @@ class MediaDataManager(
): SmartspaceMediaData {
var dismissIntent: Intent? = null
if (target.baseAction != null && target.baseAction.extras != null) {
- dismissIntent = target
- .baseAction
- .extras
- .getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent?
+ dismissIntent =
+ target.baseAction.extras.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY)
+ as Intent?
}
packageName(target)?.let {
return SmartspaceMediaData(
@@ -1289,14 +1415,16 @@ class MediaDataManager(
recommendations = target.iconGrid,
dismissIntent = dismissIntent,
headphoneConnectionTimeMillis = target.creationTimeMillis,
- instanceId = logger.getNewInstanceId())
+ instanceId = logger.getNewInstanceId()
+ )
}
- return EMPTY_SMARTSPACE_MEDIA_DATA
- .copy(targetId = target.smartspaceTargetId,
- isActive = isActive,
- dismissIntent = dismissIntent,
- headphoneConnectionTimeMillis = target.creationTimeMillis,
- instanceId = logger.getNewInstanceId())
+ return EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = target.smartspaceTargetId,
+ isActive = isActive,
+ dismissIntent = dismissIntent,
+ headphoneConnectionTimeMillis = target.creationTimeMillis,
+ instanceId = logger.getNewInstanceId()
+ )
}
private fun packageName(target: SmartspaceTarget): String? {
@@ -1308,8 +1436,9 @@ class MediaDataManager(
for (recommendation in recommendationList) {
val extras = recommendation.extras
extras?.let {
- it.getString(EXTRAS_MEDIA_SOURCE_PACKAGE_NAME)?.let {
- packageName -> return packageName }
+ it.getString(EXTRAS_MEDIA_SOURCE_PACKAGE_NAME)?.let { packageName ->
+ return packageName
+ }
}
}
Log.w(TAG, "No valid package name is provided.")
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
index b3a4ddf8ec1f..6a512be091e1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
import android.bluetooth.BluetoothLeBroadcast
import android.bluetooth.BluetoothLeBroadcastMetadata
@@ -36,6 +36,10 @@ 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
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.player.MediaDeviceData
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -47,10 +51,10 @@ 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(
+/** 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,
@@ -70,14 +74,10 @@ class MediaDeviceManager @Inject constructor(
dumpManager.registerDumpable(javaClass.name, this)
}
- /**
- * Add a listener for changes to the media route (ie. device).
- */
+ /** Add a listener for changes to the media route (ie. device). */
fun addListener(listener: Listener) = listeners.add(listener)
- /**
- * Remove a listener that has been registered with addListener.
- */
+ /** Remove a listener that has been registered with addListener. */
fun removeListener(listener: Listener) = listeners.remove(listener)
override fun onMediaDataLoaded(
@@ -101,19 +101,11 @@ class MediaDeviceManager @Inject constructor(
processDevice(key, oldKey, data.device)
return
}
- val controller = data.token?.let {
- controllerFactory.create(it)
- }
+ val controller = data.token?.let { controllerFactory.create(it) }
val localMediaManager = localMediaManagerFactory.create(data.packageName)
val muteAwaitConnectionManager =
- muteAwaitConnectionManagerFactory.create(localMediaManager)
- entry = Entry(
- key,
- oldKey,
- controller,
- localMediaManager,
- muteAwaitConnectionManager
- )
+ muteAwaitConnectionManagerFactory.create(localMediaManager)
+ entry = Entry(key, oldKey, controller, localMediaManager, muteAwaitConnectionManager)
entries[key] = entry
entry.start()
}
@@ -122,11 +114,7 @@ class MediaDeviceManager @Inject constructor(
override fun onMediaDataRemoved(key: String) {
val token = entries.remove(key)
token?.stop()
- token?.let {
- listeners.forEach {
- it.onKeyRemoved(key)
- }
- }
+ token?.let { listeners.forEach { it.onKeyRemoved(key) } }
}
override fun dump(pw: PrintWriter, args: Array<String>) {
@@ -141,9 +129,7 @@ class MediaDeviceManager @Inject constructor(
@MainThread
private fun processDevice(key: String, oldKey: String?, device: MediaDeviceData?) {
- listeners.forEach {
- it.onMediaDeviceChanged(key, oldKey, device)
- }
+ listeners.forEach { it.onMediaDeviceChanged(key, oldKey, device) }
}
interface Listener {
@@ -159,8 +145,10 @@ class MediaDeviceManager @Inject constructor(
val controller: MediaController?,
val localMediaManager: LocalMediaManager,
val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
- ) : LocalMediaManager.DeviceCallback, MediaController.Callback(),
- BluetoothLeBroadcast.Callback {
+ ) :
+ LocalMediaManager.DeviceCallback,
+ MediaController.Callback(),
+ BluetoothLeBroadcast.Callback {
val token
get() = controller?.sessionToken
@@ -171,54 +159,52 @@ class MediaDeviceManager @Inject constructor(
val sameWithoutIcon = value != null && value.equalsWithoutIcon(field)
if (!started || !sameWithoutIcon) {
field = value
- fgExecutor.execute {
- processDevice(key, oldKey, value)
- }
+ fgExecutor.execute { processDevice(key, oldKey, value) }
}
}
// 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()
+ private val configListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onLocaleListChanged() {
+ updateCurrent()
+ }
}
- }
@AnyThread
- fun start() = bgExecutor.execute {
- if (!started) {
- localMediaManager.registerCallback(this)
- localMediaManager.startScan()
- muteAwaitConnectionManager?.startListening()
- playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
- controller?.registerCallback(this)
- updateCurrent()
- started = true
- configurationController.addCallback(configListener)
+ fun start() =
+ bgExecutor.execute {
+ if (!started) {
+ localMediaManager.registerCallback(this)
+ localMediaManager.startScan()
+ muteAwaitConnectionManager?.startListening()
+ playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
+ controller?.registerCallback(this)
+ updateCurrent()
+ started = true
+ configurationController.addCallback(configListener)
+ }
}
- }
@AnyThread
- fun stop() = bgExecutor.execute {
- if (started) {
- started = false
- controller?.unregisterCallback(this)
- localMediaManager.stopScan()
- localMediaManager.unregisterCallback(this)
- muteAwaitConnectionManager?.stopListening()
- configurationController.removeCallback(configListener)
+ fun stop() =
+ bgExecutor.execute {
+ if (started) {
+ started = false
+ controller?.unregisterCallback(this)
+ localMediaManager.stopScan()
+ localMediaManager.unregisterCallback(this)
+ muteAwaitConnectionManager?.stopListening()
+ configurationController.removeCallback(configListener)
+ }
}
- }
fun dump(pw: PrintWriter) {
- val routingSession = controller?.let {
- mr2manager.getRoutingSessionForMediaController(it)
- }
- val selectedRoutes = routingSession?.let {
- mr2manager.getSelectedRoutes(it)
- }
+ val routingSession =
+ controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
+ val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) }
with(pw) {
println(" current device is ${current?.name}")
val type = controller?.playbackInfo?.playbackType
@@ -238,14 +224,11 @@ class MediaDeviceManager @Inject constructor(
updateCurrent()
}
- override fun onDeviceListUpdate(devices: List<MediaDevice>?) = bgExecutor.execute {
- updateCurrent()
- }
+ override fun onDeviceListUpdate(devices: List<MediaDevice>?) =
+ bgExecutor.execute { updateCurrent() }
override fun onSelectedDeviceStateChanged(device: MediaDevice, state: Int) {
- bgExecutor.execute {
- updateCurrent()
- }
+ bgExecutor.execute { updateCurrent() }
}
override fun onAboutToConnectDeviceAdded(
@@ -253,14 +236,17 @@ class MediaDeviceManager @Inject constructor(
deviceName: String,
deviceIcon: Drawable?
) {
- aboutToConnectDeviceOverride = AboutToConnectDevice(
- fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress),
- backupMediaDeviceData = MediaDeviceData(
- /* enabled */ enabled = true,
- /* icon */ deviceIcon,
- /* name */ deviceName,
- /* showBroadcastButton */ showBroadcastButton = false)
- )
+ aboutToConnectDeviceOverride =
+ AboutToConnectDevice(
+ fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress),
+ backupMediaDeviceData =
+ MediaDeviceData(
+ /* enabled */ enabled = true,
+ /* icon */ deviceIcon,
+ /* name */ deviceName,
+ /* showBroadcastButton */ showBroadcastButton = false
+ )
+ )
updateCurrent()
}
@@ -287,8 +273,11 @@ class MediaDeviceManager @Inject constructor(
metadata: BluetoothLeBroadcastMetadata
) {
if (DEBUG) {
- Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
- "metadata = $metadata")
+ Log.d(
+ TAG,
+ "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
+ "metadata = $metadata"
+ )
}
updateCurrent()
}
@@ -315,8 +304,10 @@ class MediaDeviceManager @Inject constructor(
override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {
if (DEBUG) {
- Log.d(TAG, "onBroadcastUpdateFailed(), reason = $reason , " +
- "broadcastId = $broadcastId")
+ Log.d(
+ TAG,
+ "onBroadcastUpdateFailed(), reason = $reason , " + "broadcastId = $broadcastId"
+ )
}
}
@@ -327,34 +318,45 @@ class MediaDeviceManager @Inject constructor(
@WorkerThread
private fun updateCurrent() {
if (isLeAudioBroadcastEnabled()) {
- current = MediaDeviceData(
+ current =
+ MediaDeviceData(
/* enabled */ true,
/* icon */ context.getDrawable(R.drawable.settings_input_antenna),
/* name */ broadcastDescription,
/* intent */ null,
- /* showBroadcastButton */ showBroadcastButton = true)
+ /* showBroadcastButton */ showBroadcastButton = true
+ )
} else {
val aboutToConnect = aboutToConnectDeviceOverride
- if (aboutToConnect != null &&
+ if (
+ aboutToConnect != null &&
aboutToConnect.fullMediaDevice == null &&
- aboutToConnect.backupMediaDeviceData != null) {
+ aboutToConnect.backupMediaDeviceData != null
+ ) {
// Only use [backupMediaDeviceData] when we don't have [fullMediaDevice].
current = aboutToConnect.backupMediaDeviceData
return
}
- val device = aboutToConnect?.fullMediaDevice
- ?: localMediaManager.currentConnectedDevice
+ 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 = if (controller == null || route != null) {
- route?.name?.toString() ?: device?.name
- } else {
- null
- }
- current = MediaDeviceData(enabled, device?.iconWithoutBackground, name,
- id = device?.id, showBroadcastButton = false)
+ val name =
+ if (controller == null || route != null) {
+ route?.name?.toString() ?: device?.name
+ } else {
+ null
+ }
+ current =
+ MediaDeviceData(
+ enabled,
+ device?.iconWithoutBackground,
+ name,
+ id = device?.id,
+ showBroadcastButton = false
+ )
}
}
@@ -384,13 +386,16 @@ class MediaDeviceManager @Inject constructor(
// 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 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)
+ broadcastDescription =
+ context.getString(R.string.broadcasting_description_is_broadcasting)
} else {
broadcastDescription = currentBroadcastedApp
}
@@ -403,9 +408,9 @@ class MediaDeviceManager @Inject constructor(
* [LocalMediaManager.DeviceCallback.onAboutToConnectDeviceAdded] for more information.
*
* @property fullMediaDevice a full-fledged [MediaDevice] object representing the device. If
- * non-null, prefer using [fullMediaDevice] over [backupMediaDeviceData].
+ * non-null, prefer using [fullMediaDevice] over [backupMediaDeviceData].
* @property backupMediaDeviceData a backup [MediaDeviceData] object containing the minimum
- * information required to display the device. Only use if [fullMediaDevice] is null.
+ * information required to display the device. Only use if [fullMediaDevice] is null.
*/
private data class AboutToConnectDevice(
val fullMediaDevice: MediaDevice? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
index 31792967899d..ab93b292308e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
import android.content.ComponentName
import android.content.Context
@@ -25,6 +25,8 @@ import android.media.session.MediaSessionManager
import android.util.Log
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -38,7 +40,9 @@ private const val TAG = "MediaSessionBasedFilter"
* sessions. In this situation, there should only be a media object for the remote session. To
* achieve this, update events for the local session need to be filtered.
*/
-class MediaSessionBasedFilter @Inject constructor(
+class MediaSessionBasedFilter
+@Inject
+constructor(
context: Context,
private val sessionManager: MediaSessionManager,
@Main private val foregroundExecutor: Executor,
@@ -50,7 +54,7 @@ class MediaSessionBasedFilter @Inject constructor(
// Keep track of MediaControllers for a given package to check if an app is casting and it
// filter loaded events for local sessions.
private val packageControllers: LinkedHashMap<String, MutableList<MediaController>> =
- LinkedHashMap()
+ LinkedHashMap()
// Keep track of the key used for the session tokens. This information is used to know when to
// dispatch a removed event so that a media object for a local session will be removed.
@@ -59,11 +63,12 @@ class MediaSessionBasedFilter @Inject constructor(
// Keep track of which media session tokens have associated notifications.
private val tokensWithNotifications: MutableSet<MediaSession.Token> = mutableSetOf()
- private val sessionListener = object : MediaSessionManager.OnActiveSessionsChangedListener {
- override fun onActiveSessionsChanged(controllers: List<MediaController>) {
- handleControllersChanged(controllers)
+ private val sessionListener =
+ object : MediaSessionManager.OnActiveSessionsChangedListener {
+ override fun onActiveSessionsChanged(controllers: List<MediaController>) {
+ handleControllersChanged(controllers)
+ }
}
- }
init {
backgroundExecutor.execute {
@@ -73,14 +78,10 @@ class MediaSessionBasedFilter @Inject constructor(
}
}
- /**
- * Add a listener for filtered [MediaData] changes
- */
+ /** Add a listener for filtered [MediaData] changes */
fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
- /**
- * Remove a listener that was registered with addListener
- */
+ /** Remove a listener that was registered with addListener */
fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
/**
@@ -100,31 +101,32 @@ class MediaSessionBasedFilter @Inject constructor(
isSsReactivated: Boolean
) {
backgroundExecutor.execute {
- data.token?.let {
- tokensWithNotifications.add(it)
- }
+ data.token?.let { tokensWithNotifications.add(it) }
val isMigration = oldKey != null && key != oldKey
if (isMigration) {
keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) }
}
if (data.token != null) {
- keyedTokens.get(key)?.let {
- tokens ->
- tokens.add(data.token)
- } ?: run {
- val tokens = mutableSetOf(data.token)
- keyedTokens.put(key, tokens)
- }
+ keyedTokens.get(key)?.let { tokens -> tokens.add(data.token) }
+ ?: run {
+ val tokens = mutableSetOf(data.token)
+ keyedTokens.put(key, tokens)
+ }
}
// Determine if an app is casting by checking if it has a session with playback type
// PLAYBACK_TYPE_REMOTE.
- val remoteControllers = packageControllers.get(data.packageName)?.filter {
- it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
- }
+ val remoteControllers =
+ packageControllers.get(data.packageName)?.filter {
+ it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
+ }
// Limiting search to only apps with a single remote session.
val remote = if (remoteControllers?.size == 1) remoteControllers.firstOrNull() else null
- if (isMigration || remote == null || remote.sessionToken == data.token ||
- !tokensWithNotifications.contains(remote.sessionToken)) {
+ if (
+ isMigration ||
+ remote == null ||
+ remote.sessionToken == data.token ||
+ !tokensWithNotifications.contains(remote.sessionToken)
+ ) {
// Not filtering in this case. Passing the event along to listeners.
dispatchMediaDataLoaded(key, oldKey, data, immediately)
} else {
@@ -146,9 +148,7 @@ class MediaSessionBasedFilter @Inject constructor(
data: SmartspaceMediaData,
shouldPrioritize: Boolean
) {
- backgroundExecutor.execute {
- dispatchSmartspaceMediaDataLoaded(key, data)
- }
+ backgroundExecutor.execute { dispatchSmartspaceMediaDataLoaded(key, data) }
}
override fun onMediaDataRemoved(key: String) {
@@ -160,9 +160,7 @@ class MediaSessionBasedFilter @Inject constructor(
}
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
- backgroundExecutor.execute {
- dispatchSmartspaceMediaDataRemoved(key, immediately)
- }
+ backgroundExecutor.execute { dispatchSmartspaceMediaDataRemoved(key, immediately) }
}
private fun dispatchMediaDataLoaded(
@@ -177,9 +175,7 @@ class MediaSessionBasedFilter @Inject constructor(
}
private fun dispatchMediaDataRemoved(key: String) {
- foregroundExecutor.execute {
- listeners.toSet().forEach { it.onMediaDataRemoved(key) }
- }
+ foregroundExecutor.execute { listeners.toSet().forEach { it.onMediaDataRemoved(key) } }
}
private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) {
@@ -196,15 +192,12 @@ class MediaSessionBasedFilter @Inject constructor(
private fun handleControllersChanged(controllers: List<MediaController>) {
packageControllers.clear()
- controllers.forEach {
- controller ->
- packageControllers.get(controller.packageName)?.let {
- tokens ->
- tokens.add(controller)
- } ?: run {
- val tokens = mutableListOf(controller)
- packageControllers.put(controller.packageName, tokens)
- }
+ controllers.forEach { controller ->
+ packageControllers.get(controller.packageName)?.let { tokens -> tokens.add(controller) }
+ ?: run {
+ val tokens = mutableListOf(controller)
+ packageControllers.put(controller.packageName, tokens)
+ }
}
tokensWithNotifications.retainAll(controllers.map { it.sessionToken })
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
index 93a29ef03393..7f5c82fb5eee 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
import android.media.session.MediaController
import android.media.session.PlaybackState
@@ -22,6 +22,8 @@ import android.os.SystemProperties
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -31,18 +33,18 @@ import java.util.concurrent.TimeUnit
import javax.inject.Inject
@VisibleForTesting
-val PAUSED_MEDIA_TIMEOUT = SystemProperties
- .getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
+val PAUSED_MEDIA_TIMEOUT =
+ SystemProperties.getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
@VisibleForTesting
-val RESUME_MEDIA_TIMEOUT = SystemProperties
- .getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3))
+val RESUME_MEDIA_TIMEOUT =
+ SystemProperties.getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3))
-/**
- * Controller responsible for keeping track of playback states and expiring inactive streams.
- */
+/** Controller responsible for keeping track of playback states and expiring inactive streams. */
@SysUISingleton
-class MediaTimeoutListener @Inject constructor(
+class MediaTimeoutListener
+@Inject
+constructor(
private val mediaControllerFactory: MediaControllerFactory,
@Main private val mainExecutor: DelayableExecutor,
private val logger: MediaTimeoutLogger,
@@ -56,7 +58,9 @@ class MediaTimeoutListener @Inject constructor(
* Callback representing that a media object is now expired:
* @param key Media control unique identifier
* @param timedOut True when expired for {@code PAUSED_MEDIA_TIMEOUT} for active media,
+ * ```
* or {@code RESUME_MEDIA_TIMEOUT} for resume media
+ * ```
*/
lateinit var timeoutCallback: (String, Boolean) -> Unit
@@ -68,21 +72,25 @@ class MediaTimeoutListener @Inject constructor(
lateinit var stateCallback: (String, PlaybackState) -> Unit
init {
- statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
- override fun onDozingChanged(isDozing: Boolean) {
- if (!isDozing) {
- // Check whether any timeouts should have expired
- mediaListeners.forEach { (key, listener) ->
- if (listener.cancellation != null &&
- listener.expiration <= systemClock.elapsedRealtime()) {
- // We dozed too long - timeout now, and cancel the pending one
- listener.expireMediaTimeout(key, "timeout happened while dozing")
- listener.doTimeout()
+ statusBarStateController.addCallback(
+ object : StatusBarStateController.StateListener {
+ override fun onDozingChanged(isDozing: Boolean) {
+ if (!isDozing) {
+ // Check whether any timeouts should have expired
+ mediaListeners.forEach { (key, listener) ->
+ if (
+ listener.cancellation != null &&
+ listener.expiration <= systemClock.elapsedRealtime()
+ ) {
+ // We dozed too long - timeout now, and cancel the pending one
+ listener.expireMediaTimeout(key, "timeout happened while dozing")
+ listener.doTimeout()
+ }
}
}
}
}
- })
+ )
}
override fun onMediaDataLoaded(
@@ -145,10 +153,8 @@ class MediaTimeoutListener @Inject constructor(
return mediaListeners[key]?.timedOut ?: false
}
- private inner class PlaybackStateListener(
- var key: String,
- data: MediaData
- ) : MediaController.Callback() {
+ private inner class PlaybackStateListener(var key: String, data: MediaData) :
+ MediaController.Callback() {
var timedOut = false
var lastState: PlaybackState? = null
@@ -162,11 +168,12 @@ class MediaTimeoutListener @Inject constructor(
mediaController?.unregisterCallback(this)
field = value
val token = field.token
- mediaController = if (token != null) {
- mediaControllerFactory.create(token)
- } else {
- null
- }
+ mediaController =
+ if (token != null) {
+ mediaControllerFactory.create(token)
+ } else {
+ null
+ }
mediaController?.registerCallback(this)
// Let's register the cancellations, but not dispatch events now.
// Timeouts didn't happen yet and reentrant events are troublesome.
@@ -212,7 +219,8 @@ class MediaTimeoutListener @Inject constructor(
logger.logPlaybackState(key, state)
val playingStateSame = (state?.state?.isPlaying() == isPlaying())
- val actionsSame = (lastState?.actions == state?.actions) &&
+ val actionsSame =
+ (lastState?.actions == state?.actions) &&
areCustomActionListsEqual(lastState?.customActions, state?.customActions)
val resumptionChanged = resumption != mediaData.resumption
@@ -237,15 +245,14 @@ class MediaTimeoutListener @Inject constructor(
return
}
expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption")
- val timeout = if (mediaData.resumption) {
- RESUME_MEDIA_TIMEOUT
- } else {
- PAUSED_MEDIA_TIMEOUT
- }
+ val timeout =
+ if (mediaData.resumption) {
+ RESUME_MEDIA_TIMEOUT
+ } else {
+ PAUSED_MEDIA_TIMEOUT
+ }
expiration = systemClock.elapsedRealtime() + timeout
- cancellation = mainExecutor.executeDelayed({
- doTimeout()
- }, timeout)
+ cancellation = mainExecutor.executeDelayed({ doTimeout() }, timeout)
} else {
expireMediaTimeout(key, "playback started - $state, $key")
timedOut = false
@@ -301,9 +308,11 @@ class MediaTimeoutListener @Inject constructor(
firstAction: PlaybackState.CustomAction,
secondAction: PlaybackState.CustomAction
): Boolean {
- if (firstAction.action != secondAction.action ||
+ if (
+ firstAction.action != secondAction.action ||
firstAction.name != secondAction.name ||
- firstAction.icon != secondAction.icon) {
+ firstAction.icon != secondAction.icon
+ ) {
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
new file mode 100644
index 000000000000..8f3f0548230f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.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.media.controls.pipeline
+
+import android.media.session.PlaybackState
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.MediaTimeoutListenerLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
+
+private const val TAG = "MediaTimeout"
+
+/** A buffered log for [MediaTimeoutListener] events */
+@SysUISingleton
+class MediaTimeoutLogger
+@Inject
+constructor(@MediaTimeoutListenerLog private val buffer: LogBuffer) {
+ fun logReuseListener(key: String) =
+ buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "reuse listener: $str1" })
+
+ fun logMigrateListener(oldKey: String?, newKey: String?, hadListener: Boolean) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = oldKey
+ str2 = newKey
+ bool1 = hadListener
+ },
+ { "migrate from $str1 to $str2, had listener? $bool1" }
+ )
+
+ fun logUpdateListener(key: String, wasPlaying: Boolean) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ bool1 = wasPlaying
+ },
+ { "updating $str1, was playing? $bool1" }
+ )
+
+ fun logDelayedUpdate(key: String) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = key },
+ { "deliver delayed playback state for $str1" }
+ )
+
+ fun logSessionDestroyed(key: String) =
+ buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "session destroyed $str1" })
+
+ fun logPlaybackState(key: String, state: PlaybackState?) =
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = key
+ str2 = state?.toString()
+ },
+ { "state update: key=$str1 state=$str2" }
+ )
+
+ fun logStateCallback(key: String) =
+ buffer.log(TAG, LogLevel.VERBOSE, { str1 = key }, { "dispatching state update for $key" })
+
+ fun logScheduleTimeout(key: String, playing: Boolean, resumption: Boolean) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ bool1 = playing
+ bool2 = resumption
+ },
+ { "schedule timeout $str1, playing=$bool1 resumption=$bool2" }
+ )
+
+ fun logCancelIgnored(key: String) =
+ buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "cancellation already exists for $str1" })
+
+ fun logTimeout(key: String) =
+ buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "execute timeout for $str1" })
+
+ fun logTimeoutCancelled(key: String, reason: String) =
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = key
+ str2 = reason
+ },
+ { "media timeout cancelled for $str1, reason: $str2" }
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaBrowserFactory.java
index aca033e99623..00620b5b2575 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaBrowserFactory.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media;
+package com.android.systemui.media.controls.resume;
import android.content.ComponentName;
import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index cc06b6c67879..4891297dbcf9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.resume
import android.content.BroadcastReceiver
import android.content.ComponentName
@@ -33,6 +33,9 @@ import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Utils
import com.android.systemui.util.time.SystemClock
@@ -47,7 +50,9 @@ private const val MEDIA_PREFERENCES = "media_control_prefs"
private const val MEDIA_PREFERENCE_KEY = "browser_components_"
@SysUISingleton
-class MediaResumeListener @Inject constructor(
+class MediaResumeListener
+@Inject
+constructor(
private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
@Background private val backgroundExecutor: Executor,
@@ -59,7 +64,7 @@ class MediaResumeListener @Inject constructor(
private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
private val resumeComponents: ConcurrentLinkedQueue<Pair<ComponentName, Long>> =
- ConcurrentLinkedQueue()
+ ConcurrentLinkedQueue()
private lateinit var mediaDataManager: MediaDataManager
@@ -72,40 +77,49 @@ class MediaResumeListener @Inject constructor(
private var currentUserId: Int = context.userId
@VisibleForTesting
- val userChangeReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (Intent.ACTION_USER_UNLOCKED == intent.action) {
- loadMediaResumptionControls()
- } else if (Intent.ACTION_USER_SWITCHED == intent.action) {
- currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
- loadSavedComponents()
+ val userChangeReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (Intent.ACTION_USER_UNLOCKED == intent.action) {
+ loadMediaResumptionControls()
+ } else if (Intent.ACTION_USER_SWITCHED == intent.action) {
+ currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
+ loadSavedComponents()
+ }
}
}
- }
- private val mediaBrowserCallback = object : ResumeMediaBrowser.Callback() {
- override fun addTrack(
- desc: MediaDescription,
- component: ComponentName,
- browser: ResumeMediaBrowser
- ) {
- val token = browser.token
- val appIntent = browser.appIntent
- val pm = context.getPackageManager()
- var appName: CharSequence = component.packageName
- val resumeAction = getResumeAction(component)
- try {
- appName = pm.getApplicationLabel(
- pm.getApplicationInfo(component.packageName, 0))
- } catch (e: PackageManager.NameNotFoundException) {
- Log.e(TAG, "Error getting package information", e)
- }
+ private val mediaBrowserCallback =
+ object : ResumeMediaBrowser.Callback() {
+ override fun addTrack(
+ desc: MediaDescription,
+ component: ComponentName,
+ browser: ResumeMediaBrowser
+ ) {
+ val token = browser.token
+ val appIntent = browser.appIntent
+ val pm = context.getPackageManager()
+ var appName: CharSequence = component.packageName
+ val resumeAction = getResumeAction(component)
+ try {
+ appName =
+ pm.getApplicationLabel(pm.getApplicationInfo(component.packageName, 0))
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(TAG, "Error getting package information", e)
+ }
- Log.d(TAG, "Adding resume controls $desc")
- mediaDataManager.addResumptionControls(currentUserId, desc, resumeAction, token,
- appName.toString(), appIntent, component.packageName)
+ Log.d(TAG, "Adding resume controls $desc")
+ mediaDataManager.addResumptionControls(
+ currentUserId,
+ desc,
+ resumeAction,
+ token,
+ appName.toString(),
+ appIntent,
+ component.packageName
+ )
+ }
}
- }
init {
if (useMediaResumption) {
@@ -113,8 +127,12 @@ class MediaResumeListener @Inject constructor(
val unlockFilter = IntentFilter()
unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED)
unlockFilter.addAction(Intent.ACTION_USER_SWITCHED)
- broadcastDispatcher.registerReceiver(userChangeReceiver, unlockFilter, null,
- UserHandle.ALL)
+ broadcastDispatcher.registerReceiver(
+ userChangeReceiver,
+ unlockFilter,
+ null,
+ UserHandle.ALL
+ )
loadSavedComponents()
}
}
@@ -123,12 +141,15 @@ class MediaResumeListener @Inject constructor(
mediaDataManager = manager
// Add listener for resumption setting changes
- tunerService.addTunable(object : TunerService.Tunable {
- override fun onTuningChanged(key: String?, newValue: String?) {
- useMediaResumption = Utils.useMediaResumption(context)
- mediaDataManager.setMediaResumptionEnabled(useMediaResumption)
- }
- }, Settings.Secure.MEDIA_CONTROLS_RESUME)
+ tunerService.addTunable(
+ object : TunerService.Tunable {
+ override fun onTuningChanged(key: String?, newValue: String?) {
+ useMediaResumption = Utils.useMediaResumption(context)
+ mediaDataManager.setMediaResumptionEnabled(useMediaResumption)
+ }
+ },
+ Settings.Secure.MEDIA_CONTROLS_RESUME
+ )
}
private fun loadSavedComponents() {
@@ -136,8 +157,10 @@ class MediaResumeListener @Inject constructor(
resumeComponents.clear()
val prefs = context.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE)
val listString = prefs.getString(MEDIA_PREFERENCE_KEY + currentUserId, null)
- val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex())
- ?.dropLastWhile { it.isEmpty() }
+ val components =
+ listString?.split(ResumeMediaBrowser.DELIMITER.toRegex())?.dropLastWhile {
+ it.isEmpty()
+ }
var needsUpdate = false
components?.forEach {
val info = it.split("/")
@@ -145,17 +168,18 @@ class MediaResumeListener @Inject constructor(
val className = info[1]
val component = ComponentName(packageName, className)
- val lastPlayed = if (info.size == 3) {
- try {
- info[2].toLong()
- } catch (e: NumberFormatException) {
+ val lastPlayed =
+ if (info.size == 3) {
+ try {
+ info[2].toLong()
+ } catch (e: NumberFormatException) {
+ needsUpdate = true
+ systemClock.currentTimeMillis()
+ }
+ } else {
needsUpdate = true
systemClock.currentTimeMillis()
}
- } else {
- needsUpdate = true
- systemClock.currentTimeMillis()
- }
resumeComponents.add(component to lastPlayed)
}
Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}")
@@ -166,9 +190,7 @@ class MediaResumeListener @Inject constructor(
}
}
- /**
- * Load controls for resuming media, if available
- */
+ /** Load controls for resuming media, if available */
private fun loadMediaResumptionControls() {
if (!useMediaResumption) {
return
@@ -204,9 +226,7 @@ class MediaResumeListener @Inject constructor(
val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
val resumeInfo = pm.queryIntentServices(serviceIntent, 0)
- val inf = resumeInfo?.filter {
- it.serviceInfo.packageName == data.packageName
- }
+ val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName }
if (inf != null && inf.size > 0) {
backgroundExecutor.execute {
tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName)
@@ -227,7 +247,8 @@ class MediaResumeListener @Inject constructor(
Log.d(TAG, "Testing if we can connect to $componentName")
// Set null action to prevent additional attempts to connect
mediaDataManager.setResumeAction(key, null)
- mediaBrowser = mediaBrowserFactory.create(
+ mediaBrowser =
+ mediaBrowserFactory.create(
object : ResumeMediaBrowser.Callback() {
override fun onConnected() {
Log.d(TAG, "Connected to $componentName")
@@ -250,7 +271,8 @@ class MediaResumeListener @Inject constructor(
mediaBrowser = null
}
},
- componentName)
+ componentName
+ )
mediaBrowser?.testConnection()
}
@@ -285,9 +307,7 @@ class MediaResumeListener @Inject constructor(
prefs.edit().putString(MEDIA_PREFERENCE_KEY + currentUserId, sb.toString()).apply()
}
- /**
- * Get a runnable which will resume media playback
- */
+ /** Get a runnable which will resume media playback */
private fun getResumeAction(componentName: ComponentName): Runnable {
return Runnable {
mediaBrowser = mediaBrowserFactory.create(null, componentName)
@@ -296,8 +316,6 @@ class MediaResumeListener @Inject constructor(
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.apply {
- println("resumeComponents: $resumeComponents")
- }
+ pw.apply { println("resumeComponents: $resumeComponents") }
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
index 40a5653a15a0..3493b2453fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media;
+package com.android.systemui.media.controls.resume;
import android.annotation.Nullable;
import android.app.PendingIntent;
@@ -293,7 +293,7 @@ public class ResumeMediaBrowser {
public PendingIntent getAppIntent() {
PackageManager pm = mContext.getPackageManager();
Intent launchIntent = pm.getLaunchIntentForPackage(mComponentName.getPackageName());
- return PendingIntent.getActivity(mContext, 0, launchIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ return PendingIntent.getActivity(mContext, 0, launchIntent, PendingIntent.FLAG_IMMUTABLE);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java
index 3d1380b6bd24..c558227df0b5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media;
+package com.android.systemui.media.controls.resume;
import android.content.ComponentName;
import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
index 41f735486c7e..335ce1d3d694 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
@@ -14,61 +14,60 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.resume
import android.content.ComponentName
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaBrowserLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** A logger for events in [ResumeMediaBrowser]. */
@SysUISingleton
-class ResumeMediaBrowserLogger @Inject constructor(
- @MediaBrowserLog private val buffer: LogBuffer
-) {
+class ResumeMediaBrowserLogger @Inject constructor(@MediaBrowserLog private val buffer: LogBuffer) {
/** Logs that we've initiated a connection to a [android.media.browse.MediaBrowser]. */
- fun logConnection(componentName: ComponentName, reason: String) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = componentName.toShortString()
- str2 = reason
- },
- { "Connecting browser for component $str1 due to $str2" }
- )
+ fun logConnection(componentName: ComponentName, reason: String) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = componentName.toShortString()
+ str2 = reason
+ },
+ { "Connecting browser for component $str1 due to $str2" }
+ )
/** Logs that we've disconnected from a [android.media.browse.MediaBrowser]. */
- fun logDisconnect(componentName: ComponentName) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = componentName.toShortString()
- },
- { "Disconnecting browser for component $str1" }
- )
+ fun logDisconnect(componentName: ComponentName) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = componentName.toShortString() },
+ { "Disconnecting browser for component $str1" }
+ )
/**
* Logs that we received a [android.media.session.MediaController.Callback.onSessionDestroyed]
* event.
*
* @param isBrowserConnected true if there's a currently connected
+ * ```
* [android.media.browse.MediaBrowser] and false otherwise.
- * @param componentName the component name for the [ResumeMediaBrowser] that triggered this log.
+ * @param componentName
+ * ```
+ * the component name for the [ResumeMediaBrowser] that triggered this log.
*/
- fun logSessionDestroyed(
- isBrowserConnected: Boolean,
- componentName: ComponentName
- ) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- bool1 = isBrowserConnected
- str1 = componentName.toShortString()
- },
- { "Session destroyed. Active browser = $bool1. Browser component = $str1." }
- )
+ fun logSessionDestroyed(isBrowserConnected: Boolean, componentName: ComponentName) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ bool1 = isBrowserConnected
+ str1 = componentName.toShortString()
+ },
+ { "Session destroyed. Active browser = $bool1. Browser component = $str1." }
+ )
}
private const val TAG = "MediaBrowser"
diff --git a/packages/SystemUI/src/com/android/systemui/media/AnimationBindHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt
index 013683e962a4..d2793bca867b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/AnimationBindHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt
@@ -14,19 +14,23 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.graphics.drawable.Animatable2
import android.graphics.drawable.Drawable
/**
- * AnimationBindHandler is responsible for tracking the bound animation state and preventing
- * jank and conflicts due to media notifications arriving at any time during an animation. It
- * does this in two parts.
- * - Exit animations fired as a result of user input are tracked. When these are running, any
+ * AnimationBindHandler is responsible for tracking the bound animation state and preventing jank
+ * and conflicts due to media notifications arriving at any time during an animation. It does this
+ * in two parts.
+ * - Exit animations fired as a result of user input are tracked. When these are running, any
+ * ```
* bind actions are delayed until the animation completes (and then fired in sequence).
- * - Continuous animations are tracked using their rebind id. Later calls using the same
+ * ```
+ * - Continuous animations are tracked using their rebind id. Later calls using the same
+ * ```
* rebind id will be totally ignored to prevent the continuous animation from restarting.
+ * ```
*/
internal class AnimationBindHandler : Animatable2.AnimationCallback() {
private val onAnimationsComplete = mutableListOf<() -> Unit>()
@@ -37,10 +41,10 @@ internal class AnimationBindHandler : Animatable2.AnimationCallback() {
get() = registrations.any { it.isRunning }
/**
- * This check prevents rebinding to the action button if the identifier has not changed. A
- * null value is always considered to be changed. This is used to prevent the connecting
- * animation from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by
- * an application in a row.
+ * This check prevents rebinding to the action button if the identifier has not changed. A null
+ * value is always considered to be changed. This is used to prevent the connecting animation
+ * from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by an
+ * application in a row.
*/
fun updateRebindId(newRebindId: Int?): Boolean {
if (rebindId == null || newRebindId == null || rebindId != newRebindId) {
@@ -78,4 +82,4 @@ internal class AnimationBindHandler : Animatable2.AnimationCallback() {
onAnimationsComplete.clear()
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
new file mode 100644
index 000000000000..61ef2f1838e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.controls.ui
+
+import android.animation.ArgbEvaluator
+import android.animation.ValueAnimator
+import android.animation.ValueAnimator.AnimatorUpdateListener
+import android.content.Context
+import android.content.res.ColorStateList
+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.media.controls.models.player.MediaViewHolder
+import com.android.systemui.monet.ColorScheme
+
+/**
+ * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
+ * is triggered.
+ */
+interface ColorTransition {
+ fun updateColorScheme(scheme: ColorScheme?): Boolean
+}
+
+/**
+ * A [ColorTransition] that animates between two specific colors. It uses a ValueAnimator to execute
+ * the animation and interpolate between the source color and the target color.
+ *
+ * Selection of the target color from the scheme, and application of the interpolated color are
+ * delegated to callbacks.
+ */
+open class AnimatingColorTransition(
+ private val defaultColor: Int,
+ private val extractColor: (ColorScheme) -> Int,
+ private val applyColor: (Int) -> Unit
+) : AnimatorUpdateListener, ColorTransition {
+
+ private val argbEvaluator = ArgbEvaluator()
+ private val valueAnimator = buildAnimator()
+ var sourceColor: Int = defaultColor
+ var currentColor: Int = defaultColor
+ var targetColor: Int = defaultColor
+
+ override fun onAnimationUpdate(animation: ValueAnimator) {
+ currentColor =
+ argbEvaluator.evaluate(animation.animatedFraction, sourceColor, targetColor) as Int
+ applyColor(currentColor)
+ }
+
+ override fun updateColorScheme(scheme: ColorScheme?): Boolean {
+ val newTargetColor = if (scheme == null) defaultColor else extractColor(scheme)
+ if (newTargetColor != targetColor) {
+ sourceColor = currentColor
+ targetColor = newTargetColor
+ valueAnimator.cancel()
+ valueAnimator.start()
+ return true
+ }
+ return false
+ }
+
+ init {
+ applyColor(defaultColor)
+ }
+
+ @VisibleForTesting
+ open fun buildAnimator(): ValueAnimator {
+ val animator = ValueAnimator.ofFloat(0f, 1f)
+ animator.duration = 333
+ animator.addUpdateListener(this)
+ return animator
+ }
+}
+
+typealias AnimatingColorTransitionFactory =
+ (Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition
+
+/**
+ * ColorSchemeTransition constructs a ColorTransition for each color in the scheme that needs to be
+ * transitioned when changed. It also sets up the assignment functions for sending the sending the
+ * interpolated colors to the appropriate views.
+ */
+class ColorSchemeTransition
+internal constructor(
+ private val context: Context,
+ private val mediaViewHolder: MediaViewHolder,
+ animatingColorTransitionFactory: AnimatingColorTransitionFactory
+) {
+ constructor(
+ context: Context,
+ mediaViewHolder: MediaViewHolder
+ ) : this(context, mediaViewHolder, ::AnimatingColorTransition)
+
+ val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
+ val surfaceColor =
+ animatingColorTransitionFactory(bgColor, ::surfaceFromScheme) { surfaceColor ->
+ val colorList = ColorStateList.valueOf(surfaceColor)
+ mediaViewHolder.seamlessIcon.imageTintList = colorList
+ mediaViewHolder.seamlessText.setTextColor(surfaceColor)
+ mediaViewHolder.albumView.backgroundTintList = colorList
+ mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor)
+ }
+
+ val accentPrimary =
+ animatingColorTransitionFactory(
+ loadDefaultColor(R.attr.textColorPrimary),
+ ::accentPrimaryFromScheme
+ ) { accentPrimary ->
+ val accentColorList = ColorStateList.valueOf(accentPrimary)
+ mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
+ mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
+ }
+
+ val accentSecondary =
+ animatingColorTransitionFactory(
+ loadDefaultColor(R.attr.textColorPrimary),
+ ::accentSecondaryFromScheme
+ ) { accentSecondary ->
+ val colorList = ColorStateList.valueOf(accentSecondary)
+ (mediaViewHolder.seamlessButton.background as? RippleDrawable)?.let {
+ it.setColor(colorList)
+ it.effectColor = colorList
+ }
+ }
+
+ val colorSeamless =
+ animatingColorTransitionFactory(
+ loadDefaultColor(R.attr.textColorPrimary),
+ { colorScheme: ColorScheme ->
+ // A1-100 dark in dark theme, A1-200 in light theme
+ if (
+ context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
+ UI_MODE_NIGHT_YES
+ )
+ colorScheme.accent1[2]
+ else colorScheme.accent1[3]
+ },
+ { seamlessColor: Int ->
+ val accentColorList = ColorStateList.valueOf(seamlessColor)
+ mediaViewHolder.seamlessButton.backgroundTintList = accentColorList
+ }
+ )
+
+ val textPrimary =
+ animatingColorTransitionFactory(
+ loadDefaultColor(R.attr.textColorPrimary),
+ ::textPrimaryFromScheme
+ ) { textPrimary ->
+ mediaViewHolder.titleText.setTextColor(textPrimary)
+ val textColorList = ColorStateList.valueOf(textPrimary)
+ mediaViewHolder.seekBar.thumb.setTintList(textColorList)
+ mediaViewHolder.seekBar.progressTintList = textColorList
+ mediaViewHolder.scrubbingElapsedTimeView.setTextColor(textColorList)
+ mediaViewHolder.scrubbingTotalTimeView.setTextColor(textColorList)
+ for (button in mediaViewHolder.getTransparentActionButtons()) {
+ button.imageTintList = textColorList
+ }
+ mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary)
+ }
+
+ val textPrimaryInverse =
+ animatingColorTransitionFactory(
+ loadDefaultColor(R.attr.textColorPrimaryInverse),
+ ::textPrimaryInverseFromScheme
+ ) { textPrimaryInverse ->
+ mediaViewHolder.actionPlayPause.imageTintList =
+ ColorStateList.valueOf(textPrimaryInverse)
+ }
+
+ val textSecondary =
+ animatingColorTransitionFactory(
+ loadDefaultColor(R.attr.textColorSecondary),
+ ::textSecondaryFromScheme
+ ) { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) }
+
+ val textTertiary =
+ animatingColorTransitionFactory(
+ loadDefaultColor(R.attr.textColorTertiary),
+ ::textTertiaryFromScheme
+ ) { textTertiary ->
+ mediaViewHolder.seekBar.progressBackgroundTintList =
+ ColorStateList.valueOf(textTertiary)
+ }
+
+ val colorTransitions =
+ arrayOf(
+ surfaceColor,
+ colorSeamless,
+ accentPrimary,
+ accentSecondary,
+ textPrimary,
+ textPrimaryInverse,
+ textSecondary,
+ textTertiary,
+ )
+
+ private fun loadDefaultColor(id: Int): Int {
+ return Utils.getColorAttr(context, id).defaultColor
+ }
+
+ fun updateColorScheme(colorScheme: ColorScheme?): Boolean {
+ var anyChanged = false
+ colorTransitions.forEach { anyChanged = it.updateColorScheme(colorScheme) || anyChanged }
+ colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme }
+ return anyChanged
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
index 121ddd46976d..9f86cd88788b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -42,22 +42,20 @@ import org.xmlpull.v1.XmlPullParser
private const val BACKGROUND_ANIM_DURATION = 370L
-/**
- * Drawable that can draw an animated gradient when tapped.
- */
+/** Drawable that can draw an animated gradient when tapped. */
@Keep
class IlluminationDrawable : Drawable() {
private var themeAttrs: IntArray? = null
private var cornerRadiusOverride = -1f
var cornerRadius = 0f
- get() {
- return if (cornerRadiusOverride >= 0) {
- cornerRadiusOverride
- } else {
- field
+ get() {
+ return if (cornerRadiusOverride >= 0) {
+ cornerRadiusOverride
+ } else {
+ field
+ }
}
- }
private var highlightColor = Color.TRANSPARENT
private var tmpHsl = floatArrayOf(0f, 0f, 0f)
private var paint = Paint()
@@ -65,22 +63,27 @@ class IlluminationDrawable : Drawable() {
private val lightSources = arrayListOf<LightSourceDrawable>()
private var backgroundColor = Color.TRANSPARENT
- set(value) {
- if (value == field) {
- return
+ set(value) {
+ if (value == field) {
+ return
+ }
+ field = value
+ animateBackground()
}
- field = value
- animateBackground()
- }
private var backgroundAnimation: ValueAnimator? = null
- /**
- * Draw background and gradient.
- */
+ /** Draw background and gradient. */
override fun draw(canvas: Canvas) {
- canvas.drawRoundRect(0f, 0f, bounds.width().toFloat(), bounds.height().toFloat(),
- cornerRadius, cornerRadius, paint)
+ canvas.drawRoundRect(
+ 0f,
+ 0f,
+ bounds.width().toFloat(),
+ bounds.height().toFloat(),
+ cornerRadius,
+ cornerRadius,
+ paint
+ )
}
override fun getOutline(outline: Outline) {
@@ -105,12 +108,11 @@ class IlluminationDrawable : Drawable() {
private fun updateStateFromTypedArray(a: TypedArray) {
if (a.hasValue(R.styleable.IlluminationDrawable_cornerRadius)) {
- cornerRadius = a.getDimension(R.styleable.IlluminationDrawable_cornerRadius,
- cornerRadius)
+ cornerRadius =
+ a.getDimension(R.styleable.IlluminationDrawable_cornerRadius, cornerRadius)
}
if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) {
- highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) /
- 100f
+ highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f
}
}
@@ -163,34 +165,42 @@ class IlluminationDrawable : Drawable() {
private fun animateBackground() {
ColorUtils.colorToHSL(backgroundColor, tmpHsl)
val L = tmpHsl[2]
- tmpHsl[2] = MathUtils.constrain(if (L < 1f - highlight) {
- L + highlight
- } else {
- L - highlight
- }, 0f, 1f)
+ tmpHsl[2] =
+ MathUtils.constrain(
+ if (L < 1f - highlight) {
+ L + highlight
+ } else {
+ L - highlight
+ },
+ 0f,
+ 1f
+ )
val initialBackground = paint.color
val initialHighlight = highlightColor
val finalHighlight = ColorUtils.HSLToColor(tmpHsl)
backgroundAnimation?.cancel()
- backgroundAnimation = ValueAnimator.ofFloat(0f, 1f).apply {
- duration = BACKGROUND_ANIM_DURATION
- interpolator = Interpolators.FAST_OUT_LINEAR_IN
- addUpdateListener {
- val progress = it.animatedValue as Float
- paint.color = blendARGB(initialBackground, backgroundColor, progress)
- highlightColor = blendARGB(initialHighlight, finalHighlight, progress)
- lightSources.forEach { it.highlightColor = highlightColor }
- invalidateSelf()
- }
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- backgroundAnimation = null
+ backgroundAnimation =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = BACKGROUND_ANIM_DURATION
+ interpolator = Interpolators.FAST_OUT_LINEAR_IN
+ addUpdateListener {
+ val progress = it.animatedValue as Float
+ paint.color = blendARGB(initialBackground, backgroundColor, progress)
+ highlightColor = blendARGB(initialHighlight, finalHighlight, progress)
+ lightSources.forEach { it.highlightColor = highlightColor }
+ invalidateSelf()
}
- })
- start()
- }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ backgroundAnimation = null
+ }
+ }
+ )
+ start()
+ }
}
override fun setTintList(tint: ColorStateList?) {
@@ -215,4 +225,4 @@ class IlluminationDrawable : Drawable() {
fun setCornerRadiusOverride(cornerRadius: Float?) {
cornerRadiusOverride = cornerRadius ?: -1f
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
index 32600fba61a4..899148b0014c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.content.Context
import android.content.res.Configuration
@@ -45,7 +45,9 @@ import javax.inject.Named
* switches media player positioning between split pane container vs single pane container
*/
@SysUISingleton
-class KeyguardMediaController @Inject constructor(
+class KeyguardMediaController
+@Inject
+constructor(
@param:Named(KEYGUARD) private val mediaHost: MediaHost,
private val bypassController: KeyguardBypassController,
private val statusBarStateController: SysuiStatusBarStateController,
@@ -56,34 +58,40 @@ class KeyguardMediaController @Inject constructor(
) {
init {
- statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
- override fun onStateChanged(newState: Int) {
- refreshMediaPosition()
+ statusBarStateController.addCallback(
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ refreshMediaPosition()
+ }
}
- })
- configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- updateResources()
+ )
+ configurationController.addCallback(
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ updateResources()
+ }
}
- })
+ )
- val settingsObserver: ContentObserver = object : ContentObserver(handler) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- if (uri == lockScreenMediaPlayerUri) {
- allowMediaPlayerOnLockScreen =
+ val settingsObserver: ContentObserver =
+ object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ if (uri == lockScreenMediaPlayerUri) {
+ allowMediaPlayerOnLockScreen =
secureSettings.getBoolForUser(
- Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
- true,
- UserHandle.USER_CURRENT
+ Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+ true,
+ UserHandle.USER_CURRENT
)
- refreshMediaPosition()
+ refreshMediaPosition()
+ }
}
}
- }
secureSettings.registerContentObserverForUser(
- Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
- settingsObserver,
- UserHandle.USER_ALL)
+ Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+ settingsObserver,
+ UserHandle.USER_ALL
+ )
// First let's set the desired state that we want for this host
mediaHost.expansion = MediaHostState.EXPANDED
@@ -110,27 +118,21 @@ class KeyguardMediaController @Inject constructor(
refreshMediaPosition()
}
- /**
- * Is the media player visible?
- */
+ /** Is the media player visible? */
var visible = false
private set
var visibilityChangedListener: ((Boolean) -> Unit)? = null
- /**
- * single pane media container placed at the top of the notifications list
- */
+ /** single pane media container placed at the top of the notifications list */
var singlePaneContainer: MediaContainerView? = null
private set
private var splitShadeContainer: ViewGroup? = null
- /**
- * Track the media player setting status on lock screen.
- */
+ /** Track the media player setting status on lock screen. */
private var allowMediaPlayerOnLockScreen: Boolean = true
private val lockScreenMediaPlayerUri =
- secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
+ secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
/**
* Attaches media container in single pane mode, situated at the top of the notifications list
@@ -146,9 +148,7 @@ class KeyguardMediaController @Inject constructor(
onMediaHostVisibilityChanged(mediaHost.visible)
}
- /**
- * Called whenever the media hosts visibility changes
- */
+ /** Called whenever the media hosts visibility changes */
private fun onMediaHostVisibilityChanged(visible: Boolean) {
refreshMediaPosition()
if (visible) {
@@ -159,9 +159,7 @@ class KeyguardMediaController @Inject constructor(
}
}
- /**
- * Attaches media container in split shade mode, situated to the left of notifications
- */
+ /** Attaches media container in split shade mode, situated to the left of notifications */
fun attachSplitShadeContainer(container: ViewGroup) {
splitShadeContainer = container
reattachHostView()
@@ -183,9 +181,7 @@ class KeyguardMediaController @Inject constructor(
}
if (activeContainer?.childCount == 0) {
// Detach the hostView from its parent view if exists
- mediaHost.hostView.parent?.let {
- (it as? ViewGroup)?.removeView(mediaHost.hostView)
- }
+ mediaHost.hostView.parent?.let { (it as? ViewGroup)?.removeView(mediaHost.hostView) }
activeContainer.addView(mediaHost.hostView)
}
}
@@ -193,7 +189,8 @@ class KeyguardMediaController @Inject constructor(
fun refreshMediaPosition() {
val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD)
// mediaHost.visible required for proper animations handling
- visible = mediaHost.visible &&
+ visible =
+ mediaHost.visible &&
!bypassController.bypassEnabled &&
keyguardOrUserSwitcher &&
allowMediaPlayerOnLockScreen
diff --git a/packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
index 711cb361f437..dd5c2bf497cb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -55,9 +55,7 @@ private data class RippleData(
var highlight: Float
)
-/**
- * Drawable that can draw an animated gradient when tapped.
- */
+/** Drawable that can draw an animated gradient when tapped. */
@Keep
class LightSourceDrawable : Drawable() {
@@ -67,17 +65,15 @@ class LightSourceDrawable : Drawable() {
private var paint = Paint()
var highlightColor = Color.WHITE
- set(value) {
- if (field == value) {
- return
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ invalidateSelf()
}
- field = value
- invalidateSelf()
- }
- /**
- * Draw a small highlight under the finger before expanding (or cancelling) it.
- */
+ /** Draw a small highlight under the finger before expanding (or cancelling) it. */
private var active: Boolean = false
set(value) {
if (value == field) {
@@ -91,46 +87,54 @@ class LightSourceDrawable : Drawable() {
rippleData.progress = RIPPLE_DOWN_PROGRESS
} else {
rippleAnimation?.cancel()
- rippleAnimation = ValueAnimator.ofFloat(rippleData.alpha, 0f).apply {
- duration = RIPPLE_CANCEL_DURATION
- interpolator = Interpolators.LINEAR_OUT_SLOW_IN
- addUpdateListener {
- rippleData.alpha = it.animatedValue as Float
- invalidateSelf()
- }
- addListener(object : AnimatorListenerAdapter() {
- var cancelled = false
- override fun onAnimationCancel(animation: Animator?) {
- cancelled = true
- }
-
- override fun onAnimationEnd(animation: Animator?) {
- if (cancelled) {
- return
- }
- rippleData.progress = 0f
- rippleData.alpha = 0f
- rippleAnimation = null
+ rippleAnimation =
+ ValueAnimator.ofFloat(rippleData.alpha, 0f).apply {
+ duration = RIPPLE_CANCEL_DURATION
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ addUpdateListener {
+ rippleData.alpha = it.animatedValue as Float
invalidateSelf()
}
- })
- start()
- }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ var cancelled = false
+ override fun onAnimationCancel(animation: Animator?) {
+ cancelled = true
+ }
+
+ override fun onAnimationEnd(animation: Animator?) {
+ if (cancelled) {
+ return
+ }
+ rippleData.progress = 0f
+ rippleData.alpha = 0f
+ rippleAnimation = null
+ invalidateSelf()
+ }
+ }
+ )
+ start()
+ }
}
invalidateSelf()
}
private var rippleAnimation: Animator? = null
- /**
- * Draw background and gradient.
- */
+ /** Draw background and gradient. */
override fun draw(canvas: Canvas) {
val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress)
val centerColor =
- ColorUtils.setAlphaComponent(highlightColor, (rippleData.alpha * 255).toInt())
- paint.shader = RadialGradient(rippleData.x, rippleData.y, radius,
- intArrayOf(centerColor, Color.TRANSPARENT), GRADIENT_STOPS, Shader.TileMode.CLAMP)
+ ColorUtils.setAlphaComponent(highlightColor, (rippleData.alpha * 255).toInt())
+ paint.shader =
+ RadialGradient(
+ rippleData.x,
+ rippleData.y,
+ radius,
+ intArrayOf(centerColor, Color.TRANSPARENT),
+ GRADIENT_STOPS,
+ Shader.TileMode.CLAMP
+ )
canvas.drawCircle(rippleData.x, rippleData.y, radius, paint)
}
@@ -162,8 +166,8 @@ class LightSourceDrawable : Drawable() {
rippleData.maxSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMaxSize, 0f)
}
if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) {
- rippleData.highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) /
- 100f
+ rippleData.highlight =
+ a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f
}
}
@@ -193,40 +197,44 @@ class LightSourceDrawable : Drawable() {
invalidateSelf()
}
- /**
- * Draws an animated ripple that expands fading away.
- */
+ /** Draws an animated ripple that expands fading away. */
private fun illuminate() {
rippleData.alpha = 1f
invalidateSelf()
rippleAnimation?.cancel()
- rippleAnimation = AnimatorSet().apply {
- playTogether(ValueAnimator.ofFloat(1f, 0f).apply {
- startDelay = 133
- duration = RIPPLE_ANIM_DURATION - startDelay
- interpolator = Interpolators.LINEAR_OUT_SLOW_IN
- addUpdateListener {
- rippleData.alpha = it.animatedValue as Float
- invalidateSelf()
- }
- }, ValueAnimator.ofFloat(rippleData.progress, 1f).apply {
- duration = RIPPLE_ANIM_DURATION
- interpolator = Interpolators.LINEAR_OUT_SLOW_IN
- addUpdateListener {
- rippleData.progress = it.animatedValue as Float
- invalidateSelf()
- }
- })
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- rippleData.progress = 0f
- rippleAnimation = null
- invalidateSelf()
- }
- })
- start()
- }
+ rippleAnimation =
+ AnimatorSet().apply {
+ playTogether(
+ ValueAnimator.ofFloat(1f, 0f).apply {
+ startDelay = 133
+ duration = RIPPLE_ANIM_DURATION - startDelay
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ addUpdateListener {
+ rippleData.alpha = it.animatedValue as Float
+ invalidateSelf()
+ }
+ },
+ ValueAnimator.ofFloat(rippleData.progress, 1f).apply {
+ duration = RIPPLE_ANIM_DURATION
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ addUpdateListener {
+ rippleData.progress = it.animatedValue as Float
+ invalidateSelf()
+ }
+ }
+ )
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ rippleData.progress = 0f
+ rippleAnimation = null
+ invalidateSelf()
+ }
+ }
+ )
+ start()
+ }
}
override fun setHotspot(x: Float, y: Float) {
@@ -251,8 +259,13 @@ class LightSourceDrawable : Drawable() {
override fun getDirtyBounds(): Rect {
val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress)
- val bounds = Rect((rippleData.x - radius).toInt(), (rippleData.y - radius).toInt(),
- (rippleData.x + radius).toInt(), (rippleData.y + radius).toInt())
+ val bounds =
+ Rect(
+ (rippleData.x - radius).toInt(),
+ (rippleData.y - radius).toInt(),
+ (rippleData.x + radius).toInt(),
+ (rippleData.y + radius).toInt()
+ )
bounds.union(super.getDirtyBounds())
return bounds
}
@@ -293,4 +306,4 @@ class LightSourceDrawable : Drawable() {
return changed
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
new file mode 100644
index 000000000000..e38c1baaeae9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -0,0 +1,1327 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.controls.ui
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.res.ColorStateList
+import android.content.res.Configuration
+import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
+import android.util.Log
+import android.util.MathUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.PathInterpolator
+import android.widget.LinearLayout
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.InstanceId
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.ui.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
+import com.android.systemui.media.controls.util.MediaUiEventLogger
+import com.android.systemui.media.controls.util.SmallHash
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.PageIndicator
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
+import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.Utils
+import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.animation.requiresRemeasuring
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.util.traceSection
+import java.io.PrintWriter
+import java.util.TreeMap
+import javax.inject.Inject
+import javax.inject.Provider
+
+private const val TAG = "MediaCarouselController"
+private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS)
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+/**
+ * Class that is responsible for keeping the view carousel up to date. This also handles changes in
+ * state and applies them to the media carousel like the expansion.
+ */
+@SysUISingleton
+class MediaCarouselController
+@Inject
+constructor(
+ private val context: Context,
+ private val mediaControlPanelFactory: Provider<MediaControlPanel>,
+ private val visualStabilityProvider: VisualStabilityProvider,
+ private val mediaHostStatesManager: MediaHostStatesManager,
+ private val activityStarter: ActivityStarter,
+ private val systemClock: SystemClock,
+ @Main executor: DelayableExecutor,
+ private val mediaManager: MediaDataManager,
+ configurationController: ConfigurationController,
+ falsingCollector: FalsingCollector,
+ falsingManager: FalsingManager,
+ dumpManager: DumpManager,
+ private val logger: MediaUiEventLogger,
+ private val debugLogger: MediaCarouselControllerLogger
+) : Dumpable {
+ /** The current width of the carousel */
+ private var currentCarouselWidth: Int = 0
+
+ /** The current height of the carousel */
+ private var currentCarouselHeight: Int = 0
+
+ /** Are we currently showing only active players */
+ private var currentlyShowingOnlyActive: Boolean = false
+
+ /** Is the player currently visible (at the end of the transformation */
+ private var playersVisible: Boolean = false
+ /**
+ * The desired location where we'll be at the end of the transformation. Usually this matches
+ * the end location, except when we're still waiting on a state update call.
+ */
+ @MediaLocation private var desiredLocation: Int = -1
+
+ /**
+ * The ending location of the view where it ends when all animations and transitions have
+ * finished
+ */
+ @MediaLocation @VisibleForTesting var currentEndLocation: Int = -1
+
+ /**
+ * The ending location of the view where it ends when all animations and transitions have
+ * finished
+ */
+ @MediaLocation private var currentStartLocation: Int = -1
+
+ /** The progress of the transition or 1.0 if there is no transition happening */
+ private var currentTransitionProgress: Float = 1.0f
+
+ /** The measured width of the carousel */
+ private var carouselMeasureWidth: Int = 0
+
+ /** The measured height of the carousel */
+ private var carouselMeasureHeight: Int = 0
+ private var desiredHostState: MediaHostState? = null
+ private val mediaCarousel: MediaScrollView
+ val mediaCarouselScrollHandler: MediaCarouselScrollHandler
+ val mediaFrame: ViewGroup
+ @VisibleForTesting
+ lateinit var settingsButton: View
+ private set
+ private val mediaContent: ViewGroup
+ @VisibleForTesting val pageIndicator: PageIndicator
+ private val visualStabilityCallback: OnReorderingAllowedListener
+ private var needsReordering: Boolean = false
+ private var keysNeedRemoval = mutableSetOf<String>()
+ var shouldScrollToKey: Boolean = false
+ private var isRtl: Boolean = false
+ set(value) {
+ if (value != field) {
+ field = value
+ mediaFrame.layoutDirection =
+ if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR
+ mediaCarouselScrollHandler.scrollToStart()
+ }
+ }
+ private var currentlyExpanded = true
+ set(value) {
+ if (field != value) {
+ field = value
+ for (player in MediaPlayerData.players()) {
+ player.setListening(field)
+ }
+ }
+ }
+
+ companion object {
+ const val ANIMATION_BASE_DURATION = 2200f
+ const val DURATION = 167f
+ const val DETAILS_DELAY = 1067f
+ const val CONTROLS_DELAY = 1400f
+ const val PAGINATION_DELAY = 1900f
+ const val MEDIATITLES_DELAY = 1000f
+ const val MEDIACONTAINERS_DELAY = 967f
+ val TRANSFORM_BEZIER = PathInterpolator(0.68F, 0F, 0F, 1F)
+ val REVERSE_BEZIER = PathInterpolator(0F, 0.68F, 1F, 0F)
+
+ fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float {
+ val transformStartFraction = delay / ANIMATION_BASE_DURATION
+ val transformDurationFraction = duration / ANIMATION_BASE_DURATION
+ val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction)
+ return MathUtils.constrain(
+ (squishinessToTime - transformStartFraction) / transformDurationFraction,
+ 0F,
+ 1F
+ )
+ }
+ }
+
+ private val configListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onDensityOrFontScaleChanged() {
+ // System font changes should only happen when UMO is offscreen or a flicker may
+ // occur
+ updatePlayers(recreateMedia = true)
+ inflateSettingsButton()
+ }
+
+ override fun onThemeChanged() {
+ updatePlayers(recreateMedia = false)
+ inflateSettingsButton()
+ }
+
+ override fun onConfigChanged(newConfig: Configuration?) {
+ if (newConfig == null) return
+ isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
+ }
+
+ override fun onUiModeChanged() {
+ updatePlayers(recreateMedia = false)
+ inflateSettingsButton()
+ }
+ }
+
+ /**
+ * Update MediaCarouselScrollHandler.visibleToUser to reflect media card container visibility.
+ * It will be called when the container is out of view.
+ */
+ lateinit var updateUserVisibility: () -> Unit
+ lateinit var updateHostVisibility: () -> Unit
+
+ private val isReorderingAllowed: Boolean
+ get() = visualStabilityProvider.isReorderingAllowed
+
+ init {
+ dumpManager.registerDumpable(TAG, this)
+ mediaFrame = inflateMediaCarousel()
+ mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
+ pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
+ mediaCarouselScrollHandler =
+ MediaCarouselScrollHandler(
+ mediaCarousel,
+ pageIndicator,
+ executor,
+ this::onSwipeToDismiss,
+ this::updatePageIndicatorLocation,
+ this::closeGuts,
+ falsingCollector,
+ falsingManager,
+ this::logSmartspaceImpression,
+ logger
+ )
+ isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
+ inflateSettingsButton()
+ mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
+ configurationController.addCallback(configListener)
+ visualStabilityCallback = OnReorderingAllowedListener {
+ if (needsReordering) {
+ needsReordering = false
+ reorderAllPlayers(previousVisiblePlayerKey = null)
+ }
+
+ keysNeedRemoval.forEach { removePlayer(it) }
+ if (keysNeedRemoval.size > 0) {
+ // Carousel visibility may need to be updated after late removals
+ updateHostVisibility()
+ }
+ keysNeedRemoval.clear()
+
+ // Update user visibility so that no extra impression will be logged when
+ // activeMediaIndex resets to 0
+ if (this::updateUserVisibility.isInitialized) {
+ updateUserVisibility()
+ }
+
+ // Let's reset our scroll position
+ mediaCarouselScrollHandler.scrollToStart()
+ }
+ visualStabilityProvider.addPersistentReorderingAllowedListener(visualStabilityCallback)
+ mediaManager.addListener(
+ object : MediaDataManager.Listener {
+ override fun onMediaDataLoaded(
+ key: String,
+ oldKey: String?,
+ data: MediaData,
+ immediately: Boolean,
+ receivedSmartspaceCardLatency: Int,
+ isSsReactivated: Boolean
+ ) {
+ debugLogger.logMediaLoaded(key)
+ if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
+ // Log card received if a new resumable media card is added
+ MediaPlayerData.getMediaPlayer(key)?.let {
+ /* ktlint-disable max-line-length */
+ logSmartspaceCardReported(
+ 759, // SMARTSPACE_CARD_RECEIVED
+ it.mSmartspaceId,
+ it.mUid,
+ surfaces =
+ intArrayOf(
+ SysUiStatsLog
+ .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+ SysUiStatsLog
+ .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
+ SysUiStatsLog
+ .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+ ),
+ rank = MediaPlayerData.getMediaPlayerIndex(key)
+ )
+ /* ktlint-disable max-line-length */
+ }
+ if (
+ mediaCarouselScrollHandler.visibleToUser &&
+ mediaCarouselScrollHandler.visibleMediaIndex ==
+ MediaPlayerData.getMediaPlayerIndex(key)
+ ) {
+ logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
+ }
+ } else if (receivedSmartspaceCardLatency != 0) {
+ // Log resume card received if resumable media card is reactivated and
+ // resume card is ranked first
+ MediaPlayerData.players().forEachIndexed { index, it ->
+ if (it.recommendationViewHolder == null) {
+ it.mSmartspaceId =
+ SmallHash.hash(
+ it.mUid + systemClock.currentTimeMillis().toInt()
+ )
+ it.mIsImpressed = false
+ /* ktlint-disable max-line-length */
+ logSmartspaceCardReported(
+ 759, // SMARTSPACE_CARD_RECEIVED
+ it.mSmartspaceId,
+ it.mUid,
+ surfaces =
+ intArrayOf(
+ SysUiStatsLog
+ .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+ SysUiStatsLog
+ .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
+ SysUiStatsLog
+ .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+ ),
+ rank = index,
+ receivedLatencyMillis = receivedSmartspaceCardLatency
+ )
+ /* ktlint-disable max-line-length */
+ }
+ }
+ // If media container area already visible to the user, log impression for
+ // reactivated card.
+ if (
+ mediaCarouselScrollHandler.visibleToUser &&
+ !mediaCarouselScrollHandler.qsExpanded
+ ) {
+ logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
+ }
+ }
+
+ val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active
+ if (canRemove && !Utils.useMediaResumption(context)) {
+ // This view isn't playing, let's remove this! This happens e.g. when
+ // dismissing/timing out a view. We still have the data around because
+ // resumption could be on, but we should save the resources and release
+ // this.
+ if (isReorderingAllowed) {
+ onMediaDataRemoved(key)
+ } else {
+ keysNeedRemoval.add(key)
+ }
+ } else {
+ keysNeedRemoval.remove(key)
+ }
+ }
+
+ override fun onSmartspaceMediaDataLoaded(
+ key: String,
+ data: SmartspaceMediaData,
+ shouldPrioritize: Boolean
+ ) {
+ debugLogger.logRecommendationLoaded(key)
+ // Log the case where the hidden media carousel with the existed inactive resume
+ // media is shown by the Smartspace signal.
+ if (data.isActive) {
+ val hasActivatedExistedResumeMedia =
+ !mediaManager.hasActiveMedia() &&
+ mediaManager.hasAnyMedia() &&
+ shouldPrioritize
+ if (hasActivatedExistedResumeMedia) {
+ // Log resume card received if resumable media card is reactivated and
+ // recommendation card is valid and ranked first
+ MediaPlayerData.players().forEachIndexed { index, it ->
+ if (it.recommendationViewHolder == null) {
+ it.mSmartspaceId =
+ SmallHash.hash(
+ it.mUid + systemClock.currentTimeMillis().toInt()
+ )
+ it.mIsImpressed = false
+ /* ktlint-disable max-line-length */
+ logSmartspaceCardReported(
+ 759, // SMARTSPACE_CARD_RECEIVED
+ it.mSmartspaceId,
+ it.mUid,
+ surfaces =
+ intArrayOf(
+ SysUiStatsLog
+ .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+ SysUiStatsLog
+ .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
+ SysUiStatsLog
+ .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+ ),
+ rank = index,
+ receivedLatencyMillis =
+ (systemClock.currentTimeMillis() -
+ data.headphoneConnectionTimeMillis)
+ .toInt()
+ )
+ /* ktlint-disable max-line-length */
+ }
+ }
+ }
+ addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
+ MediaPlayerData.getMediaPlayer(key)?.let {
+ /* ktlint-disable max-line-length */
+ logSmartspaceCardReported(
+ 759, // SMARTSPACE_CARD_RECEIVED
+ it.mSmartspaceId,
+ it.mUid,
+ surfaces =
+ intArrayOf(
+ SysUiStatsLog
+ .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+ SysUiStatsLog
+ .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
+ SysUiStatsLog
+ .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+ ),
+ rank = MediaPlayerData.getMediaPlayerIndex(key),
+ receivedLatencyMillis =
+ (systemClock.currentTimeMillis() -
+ data.headphoneConnectionTimeMillis)
+ .toInt()
+ )
+ /* ktlint-disable max-line-length */
+ }
+ if (
+ mediaCarouselScrollHandler.visibleToUser &&
+ mediaCarouselScrollHandler.visibleMediaIndex ==
+ MediaPlayerData.getMediaPlayerIndex(key)
+ ) {
+ logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
+ }
+ } else {
+ onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
+ }
+ }
+
+ override fun onMediaDataRemoved(key: String) {
+ debugLogger.logMediaRemoved(key)
+ removePlayer(key)
+ }
+
+ override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
+ debugLogger.logRecommendationRemoved(key, immediately)
+ if (immediately || isReorderingAllowed) {
+ removePlayer(key)
+ if (!immediately) {
+ // Although it wasn't requested, we were able to process the removal
+ // immediately since reordering is allowed. So, notify hosts to update
+ if (this@MediaCarouselController::updateHostVisibility.isInitialized) {
+ updateHostVisibility()
+ }
+ }
+ } else {
+ keysNeedRemoval.add(key)
+ }
+ }
+ }
+ )
+ mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+ // The pageIndicator is not laid out yet when we get the current state update,
+ // Lets make sure we have the right dimensions
+ updatePageIndicatorLocation()
+ }
+ mediaHostStatesManager.addCallback(
+ object : MediaHostStatesManager.Callback {
+ override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) {
+ if (location == desiredLocation) {
+ onDesiredLocationChanged(desiredLocation, mediaHostState, animate = false)
+ }
+ }
+ }
+ )
+ }
+
+ private fun inflateSettingsButton() {
+ val settings =
+ LayoutInflater.from(context)
+ .inflate(R.layout.media_carousel_settings_button, mediaFrame, false) as View
+ if (this::settingsButton.isInitialized) {
+ mediaFrame.removeView(settingsButton)
+ }
+ settingsButton = settings
+ mediaFrame.addView(settingsButton)
+ mediaCarouselScrollHandler.onSettingsButtonUpdated(settings)
+ settingsButton.setOnClickListener {
+ logger.logCarouselSettings()
+ activityStarter.startActivity(settingsIntent, true /* dismissShade */)
+ }
+ }
+
+ private fun inflateMediaCarousel(): ViewGroup {
+ val mediaCarousel =
+ LayoutInflater.from(context)
+ .inflate(R.layout.media_carousel, UniqueObjectHostView(context), false) as ViewGroup
+ // Because this is inflated when not attached to the true view hierarchy, it resolves some
+ // potential issues to force that the layout direction is defined by the locale
+ // (rather than inherited from the parent, which would resolve to LTR when unattached).
+ mediaCarousel.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
+ return mediaCarousel
+ }
+
+ private fun reorderAllPlayers(
+ previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?,
+ key: String? = null
+ ) {
+ mediaContent.removeAllViews()
+ for (mediaPlayer in MediaPlayerData.players()) {
+ mediaPlayer.mediaViewHolder?.let { mediaContent.addView(it.player) }
+ ?: mediaPlayer.recommendationViewHolder?.let {
+ mediaContent.addView(it.recommendations)
+ }
+ }
+ mediaCarouselScrollHandler.onPlayersChanged()
+ MediaPlayerData.updateVisibleMediaPlayers()
+ // Automatically scroll to the active player if needed
+ if (shouldScrollToKey) {
+ shouldScrollToKey = false
+ val mediaIndex = key?.let { MediaPlayerData.getMediaPlayerIndex(it) } ?: -1
+ if (mediaIndex != -1) {
+ previousVisiblePlayerKey?.let {
+ val previousVisibleIndex =
+ MediaPlayerData.playerKeys().indexOfFirst { key -> it == key }
+ mediaCarouselScrollHandler.scrollToPlayer(previousVisibleIndex, mediaIndex)
+ }
+ ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
+ }
+ }
+ }
+
+ // Returns true if new player is added
+ private fun addOrUpdatePlayer(
+ key: String,
+ oldKey: String?,
+ data: MediaData,
+ isSsReactivated: Boolean
+ ): Boolean =
+ traceSection("MediaCarouselController#addOrUpdatePlayer") {
+ MediaPlayerData.moveIfExists(oldKey, key)
+ val existingPlayer = MediaPlayerData.getMediaPlayer(key)
+ val curVisibleMediaKey =
+ MediaPlayerData.visiblePlayerKeys()
+ .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+ if (existingPlayer == null) {
+ val newPlayer = mediaControlPanelFactory.get()
+ newPlayer.attachPlayer(
+ MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
+ )
+ newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
+ val lp =
+ LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
+ newPlayer.bindPlayer(data, key)
+ newPlayer.setListening(currentlyExpanded)
+ MediaPlayerData.addMediaPlayer(
+ key,
+ data,
+ newPlayer,
+ systemClock,
+ isSsReactivated,
+ debugLogger
+ )
+ updatePlayerToState(newPlayer, noAnimation = true)
+ // Media data added from a recommendation card should starts playing.
+ if (
+ (shouldScrollToKey && data.isPlaying == true) ||
+ (!shouldScrollToKey && data.active)
+ ) {
+ reorderAllPlayers(curVisibleMediaKey, key)
+ } else {
+ needsReordering = true
+ }
+ } else {
+ existingPlayer.bindPlayer(data, key)
+ MediaPlayerData.addMediaPlayer(
+ key,
+ data,
+ existingPlayer,
+ systemClock,
+ isSsReactivated,
+ debugLogger
+ )
+ val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
+ // In case of recommendations hits.
+ // Check the playing status of media player and the package name.
+ // To make sure we scroll to the right app's media player.
+ if (
+ isReorderingAllowed ||
+ shouldScrollToKey &&
+ data.isPlaying == true &&
+ packageName == data.packageName
+ ) {
+ reorderAllPlayers(curVisibleMediaKey, key)
+ } else {
+ needsReordering = true
+ }
+ }
+ updatePageIndicator()
+ mediaCarouselScrollHandler.onPlayersChanged()
+ mediaFrame.requiresRemeasuring = true
+ // Check postcondition: mediaContent should have the same number of children as there
+ // are
+ // elements in mediaPlayers.
+ if (MediaPlayerData.players().size != mediaContent.childCount) {
+ Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
+ }
+ return existingPlayer == null
+ }
+
+ private fun addSmartspaceMediaRecommendations(
+ key: String,
+ data: SmartspaceMediaData,
+ shouldPrioritize: Boolean
+ ) =
+ traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") {
+ if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel")
+ if (MediaPlayerData.getMediaPlayer(key) != null) {
+ Log.w(TAG, "Skip adding smartspace target in carousel")
+ return
+ }
+
+ val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
+ existingSmartspaceMediaKey?.let {
+ val removedPlayer =
+ MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey, true)
+ removedPlayer?.run {
+ debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
+ }
+ }
+
+ val newRecs = mediaControlPanelFactory.get()
+ newRecs.attachRecommendation(
+ RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
+ )
+ newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
+ val lp =
+ LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
+ newRecs.bindRecommendation(data)
+ val curVisibleMediaKey =
+ MediaPlayerData.visiblePlayerKeys()
+ .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+ MediaPlayerData.addMediaRecommendation(
+ key,
+ data,
+ newRecs,
+ shouldPrioritize,
+ systemClock,
+ debugLogger
+ )
+ updatePlayerToState(newRecs, noAnimation = true)
+ reorderAllPlayers(curVisibleMediaKey)
+ updatePageIndicator()
+ mediaFrame.requiresRemeasuring = true
+ // Check postcondition: mediaContent should have the same number of children as there
+ // are
+ // elements in mediaPlayers.
+ if (MediaPlayerData.players().size != mediaContent.childCount) {
+ Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
+ }
+ }
+
+ fun removePlayer(
+ key: String,
+ dismissMediaData: Boolean = true,
+ dismissRecommendation: Boolean = true
+ ) {
+ if (key == MediaPlayerData.smartspaceMediaKey()) {
+ MediaPlayerData.smartspaceMediaData?.let {
+ logger.logRecommendationRemoved(it.packageName, it.instanceId)
+ }
+ }
+ val removed =
+ MediaPlayerData.removeMediaPlayer(key, dismissMediaData || dismissRecommendation)
+ removed?.apply {
+ mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
+ mediaContent.removeView(removed.mediaViewHolder?.player)
+ mediaContent.removeView(removed.recommendationViewHolder?.recommendations)
+ removed.onDestroy()
+ mediaCarouselScrollHandler.onPlayersChanged()
+ updatePageIndicator()
+
+ if (dismissMediaData) {
+ // Inform the media manager of a potentially late dismissal
+ mediaManager.dismissMediaData(key, delay = 0L)
+ }
+ if (dismissRecommendation) {
+ // Inform the media manager of a potentially late dismissal
+ mediaManager.dismissSmartspaceRecommendation(key, delay = 0L)
+ }
+ }
+ }
+
+ private fun updatePlayers(recreateMedia: Boolean) {
+ pageIndicator.tintList =
+ ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
+
+ MediaPlayerData.mediaData().forEach { (key, data, isSsMediaRec) ->
+ if (isSsMediaRec) {
+ val smartspaceMediaData = MediaPlayerData.smartspaceMediaData
+ removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
+ smartspaceMediaData?.let {
+ addSmartspaceMediaRecommendations(
+ it.targetId,
+ it,
+ MediaPlayerData.shouldPrioritizeSs
+ )
+ }
+ } else {
+ val isSsReactivated = MediaPlayerData.isSsReactivated(key)
+ if (recreateMedia) {
+ removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
+ }
+ addOrUpdatePlayer(
+ key = key,
+ oldKey = null,
+ data = data,
+ isSsReactivated = isSsReactivated
+ )
+ }
+ }
+ }
+
+ private fun updatePageIndicator() {
+ val numPages = mediaContent.getChildCount()
+ pageIndicator.setNumPages(numPages)
+ if (numPages == 1) {
+ pageIndicator.setLocation(0f)
+ }
+ updatePageIndicatorAlpha()
+ }
+
+ /**
+ * Set a new interpolated state for all players. This is a state that is usually controlled by a
+ * finger movement where the user drags from one state to the next.
+ *
+ * @param startLocation the start location of our state or -1 if this is directly set
+ * @param endLocation the ending location of our state.
+ * @param progress the progress of the transition between startLocation and endlocation. If
+ * ```
+ * this is not a guided transformation, this will be 1.0f
+ * @param immediately
+ * ```
+ * should this state be applied immediately, canceling all animations?
+ */
+ fun setCurrentState(
+ @MediaLocation startLocation: Int,
+ @MediaLocation endLocation: Int,
+ progress: Float,
+ immediately: Boolean
+ ) {
+ if (
+ startLocation != currentStartLocation ||
+ endLocation != currentEndLocation ||
+ progress != currentTransitionProgress ||
+ immediately
+ ) {
+ currentStartLocation = startLocation
+ currentEndLocation = endLocation
+ currentTransitionProgress = progress
+ for (mediaPlayer in MediaPlayerData.players()) {
+ updatePlayerToState(mediaPlayer, immediately)
+ }
+ maybeResetSettingsCog()
+ updatePageIndicatorAlpha()
+ }
+ }
+
+ @VisibleForTesting
+ fun updatePageIndicatorAlpha() {
+ val hostStates = mediaHostStatesManager.mediaHostStates
+ val endIsVisible = hostStates[currentEndLocation]?.visible ?: false
+ val startIsVisible = hostStates[currentStartLocation]?.visible ?: false
+ val startAlpha = if (startIsVisible) 1.0f else 0.0f
+ // when squishing in split shade, only use endState, which keeps changing
+ // to provide squishFraction
+ val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F
+ val endAlpha =
+ (if (endIsVisible) 1.0f else 0.0f) *
+ calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION)
+ var alpha = 1.0f
+ if (!endIsVisible || !startIsVisible) {
+ var progress = currentTransitionProgress
+ if (!endIsVisible) {
+ progress = 1.0f - progress
+ }
+ // Let's fade in quickly at the end where the view is visible
+ progress =
+ MathUtils.constrain(MathUtils.map(0.95f, 1.0f, 0.0f, 1.0f, progress), 0.0f, 1.0f)
+ alpha = MathUtils.lerp(startAlpha, endAlpha, progress)
+ }
+ pageIndicator.alpha = alpha
+ }
+
+ private fun updatePageIndicatorLocation() {
+ // Update the location of the page indicator, carousel clipping
+ val translationX =
+ if (isRtl) {
+ (pageIndicator.width - currentCarouselWidth) / 2.0f
+ } else {
+ (currentCarouselWidth - pageIndicator.width) / 2.0f
+ }
+ pageIndicator.translationX = translationX + mediaCarouselScrollHandler.contentTranslation
+ val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams
+ pageIndicator.translationY =
+ (currentCarouselHeight - pageIndicator.height - layoutParams.bottomMargin).toFloat()
+ }
+
+ /** Update the dimension of this carousel. */
+ private fun updateCarouselDimensions() {
+ var width = 0
+ var height = 0
+ for (mediaPlayer in MediaPlayerData.players()) {
+ val controller = mediaPlayer.mediaViewController
+ // When transitioning the view to gone, the view gets smaller, but the translation
+ // Doesn't, let's add the translation
+ width = Math.max(width, controller.currentWidth + controller.translationX.toInt())
+ height = Math.max(height, controller.currentHeight + controller.translationY.toInt())
+ }
+ if (width != currentCarouselWidth || height != currentCarouselHeight) {
+ currentCarouselWidth = width
+ currentCarouselHeight = height
+ mediaCarouselScrollHandler.setCarouselBounds(
+ currentCarouselWidth,
+ currentCarouselHeight
+ )
+ updatePageIndicatorLocation()
+ updatePageIndicatorAlpha()
+ }
+ }
+
+ private fun maybeResetSettingsCog() {
+ val hostStates = mediaHostStatesManager.mediaHostStates
+ val endShowsActive = hostStates[currentEndLocation]?.showsOnlyActiveMedia ?: true
+ val startShowsActive =
+ hostStates[currentStartLocation]?.showsOnlyActiveMedia ?: endShowsActive
+ if (
+ currentlyShowingOnlyActive != endShowsActive ||
+ ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
+ startShowsActive != endShowsActive)
+ ) {
+ // Whenever we're transitioning from between differing states or the endstate differs
+ // we reset the translation
+ currentlyShowingOnlyActive = endShowsActive
+ mediaCarouselScrollHandler.resetTranslation(animate = true)
+ }
+ }
+
+ private fun updatePlayerToState(mediaPlayer: MediaControlPanel, noAnimation: Boolean) {
+ mediaPlayer.mediaViewController.setCurrentState(
+ startLocation = currentStartLocation,
+ endLocation = currentEndLocation,
+ transitionProgress = currentTransitionProgress,
+ applyImmediately = noAnimation
+ )
+ }
+
+ /**
+ * The desired location of this view has changed. We should remeasure the view to match the new
+ * bounds and kick off bounds animations if necessary. If an animation is happening, an
+ * animation is kicked of externally, which sets a new current state until we reach the
+ * targetState.
+ *
+ * @param desiredLocation the location we're going to
+ * @param desiredHostState the target state we're transitioning to
+ * @param animate should this be animated
+ */
+ fun onDesiredLocationChanged(
+ desiredLocation: Int,
+ desiredHostState: MediaHostState?,
+ animate: Boolean,
+ duration: Long = 200,
+ startDelay: Long = 0
+ ) =
+ traceSection("MediaCarouselController#onDesiredLocationChanged") {
+ desiredHostState?.let {
+ if (this.desiredLocation != desiredLocation) {
+ // Only log an event when location changes
+ logger.logCarouselPosition(desiredLocation)
+ }
+
+ // This is a hosting view, let's remeasure our players
+ this.desiredLocation = desiredLocation
+ this.desiredHostState = it
+ currentlyExpanded = it.expansion > 0
+
+ val shouldCloseGuts =
+ !currentlyExpanded &&
+ !mediaManager.hasActiveMediaOrRecommendation() &&
+ desiredHostState.showsOnlyActiveMedia
+
+ for (mediaPlayer in MediaPlayerData.players()) {
+ if (animate) {
+ mediaPlayer.mediaViewController.animatePendingStateChange(
+ duration = duration,
+ delay = startDelay
+ )
+ }
+ if (shouldCloseGuts && mediaPlayer.mediaViewController.isGutsVisible) {
+ mediaPlayer.closeGuts(!animate)
+ }
+
+ mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation)
+ }
+ mediaCarouselScrollHandler.showsSettingsButton = !it.showsOnlyActiveMedia
+ mediaCarouselScrollHandler.falsingProtectionNeeded = it.falsingProtectionNeeded
+ val nowVisible = it.visible
+ if (nowVisible != playersVisible) {
+ playersVisible = nowVisible
+ if (nowVisible) {
+ mediaCarouselScrollHandler.resetTranslation()
+ }
+ }
+ updateCarouselSize()
+ }
+ }
+
+ fun closeGuts(immediate: Boolean = true) {
+ MediaPlayerData.players().forEach { it.closeGuts(immediate) }
+ }
+
+ /** Update the size of the carousel, remeasuring it if necessary. */
+ private fun updateCarouselSize() {
+ val width = desiredHostState?.measurementInput?.width ?: 0
+ val height = desiredHostState?.measurementInput?.height ?: 0
+ if (
+ width != carouselMeasureWidth && width != 0 ||
+ height != carouselMeasureHeight && height != 0
+ ) {
+ carouselMeasureWidth = width
+ carouselMeasureHeight = height
+ val playerWidthPlusPadding =
+ carouselMeasureWidth +
+ context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
+ // Let's remeasure the carousel
+ val widthSpec = desiredHostState?.measurementInput?.widthMeasureSpec ?: 0
+ val heightSpec = desiredHostState?.measurementInput?.heightMeasureSpec ?: 0
+ mediaCarousel.measure(widthSpec, heightSpec)
+ mediaCarousel.layout(0, 0, width, mediaCarousel.measuredHeight)
+ // Update the padding after layout; view widths are used in RTL to calculate scrollX
+ mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding
+ }
+ }
+
+ /** Log the user impression for media card at visibleMediaIndex. */
+ fun logSmartspaceImpression(qsExpanded: Boolean) {
+ val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
+ if (MediaPlayerData.players().size > visibleMediaIndex) {
+ val mediaControlPanel = MediaPlayerData.getMediaControlPanel(visibleMediaIndex)
+ val hasActiveMediaOrRecommendationCard =
+ MediaPlayerData.hasActiveMediaOrRecommendationCard()
+ if (!hasActiveMediaOrRecommendationCard && !qsExpanded) {
+ // Skip logging if on LS or QQS, and there is no active media card
+ return
+ }
+ mediaControlPanel?.let {
+ logSmartspaceCardReported(
+ 800, // SMARTSPACE_CARD_SEEN
+ it.mSmartspaceId,
+ it.mUid,
+ intArrayOf(it.surfaceForSmartspaceLogging)
+ )
+ it.mIsImpressed = true
+ }
+ }
+ }
+
+ @JvmOverloads
+ /**
+ * Log Smartspace events
+ *
+ * @param eventId UI event id (e.g. 800 for SMARTSPACE_CARD_SEEN)
+ * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new
+ * instanceId
+ * @param uid uid for the application that media comes from
+ * @param surfaces list of display surfaces the media card is on (e.g. lockscreen, shade) when
+ * the event happened
+ * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1
+ * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc.
+ * @param interactedSubcardCardinality how many media items were shown to the user when there is
+ * user interaction
+ * @param rank the rank for media card in the media carousel, starting from 0
+ * @param receivedLatencyMillis latency in milliseconds for card received events. E.g. latency
+ * between headphone connection to sysUI displays media recommendation card
+ * @param isSwipeToDismiss whether is to log swipe-to-dismiss event
+ */
+ fun logSmartspaceCardReported(
+ eventId: Int,
+ instanceId: Int,
+ uid: Int,
+ surfaces: IntArray,
+ interactedSubcardRank: Int = 0,
+ interactedSubcardCardinality: Int = 0,
+ rank: Int = mediaCarouselScrollHandler.visibleMediaIndex,
+ receivedLatencyMillis: Int = 0,
+ isSwipeToDismiss: Boolean = false
+ ) {
+ if (MediaPlayerData.players().size <= rank) {
+ return
+ }
+
+ val mediaControlKey = MediaPlayerData.visiblePlayerKeys().elementAt(rank)
+ // Only log media resume card when Smartspace data is available
+ if (
+ !mediaControlKey.isSsMediaRec &&
+ !mediaManager.smartspaceMediaData.isActive &&
+ MediaPlayerData.smartspaceMediaData == null
+ ) {
+ return
+ }
+
+ val cardinality = mediaContent.getChildCount()
+ surfaces.forEach { surface ->
+ /* ktlint-disable max-line-length */
+ SysUiStatsLog.write(
+ SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
+ eventId,
+ instanceId,
+ // Deprecated, replaced with AiAi feature type so we don't need to create logging
+ // card type for each new feature.
+ SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
+ surface,
+ // Use -1 as rank value to indicate user swipe to dismiss the card
+ if (isSwipeToDismiss) -1 else rank,
+ cardinality,
+ if (mediaControlKey.isSsMediaRec) 15 // MEDIA_RECOMMENDATION
+ else if (mediaControlKey.isSsReactivated) 43 // MEDIA_RESUME_SS_ACTIVATED
+ else 31, // MEDIA_RESUME
+ uid,
+ interactedSubcardRank,
+ interactedSubcardCardinality,
+ receivedLatencyMillis,
+ null, // Media cards cannot have subcards.
+ null // Media cards don't have dimensions today.
+ )
+ /* ktlint-disable max-line-length */
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Log Smartspace card event id: $eventId instance id: $instanceId" +
+ " surface: $surface rank: $rank cardinality: $cardinality " +
+ "isRecommendationCard: ${mediaControlKey.isSsMediaRec} " +
+ "isSsReactivated: ${mediaControlKey.isSsReactivated}" +
+ "uid: $uid " +
+ "interactedSubcardRank: $interactedSubcardRank " +
+ "interactedSubcardCardinality: $interactedSubcardCardinality " +
+ "received_latency_millis: $receivedLatencyMillis"
+ )
+ }
+ }
+ }
+
+ private fun onSwipeToDismiss() {
+ MediaPlayerData.players().forEachIndexed { index, it ->
+ if (it.mIsImpressed) {
+ logSmartspaceCardReported(
+ SMARTSPACE_CARD_DISMISS_EVENT,
+ it.mSmartspaceId,
+ it.mUid,
+ intArrayOf(it.surfaceForSmartspaceLogging),
+ rank = index,
+ isSwipeToDismiss = true
+ )
+ // Reset card impressed state when swipe to dismissed
+ it.mIsImpressed = false
+ }
+ }
+ logger.logSwipeDismiss()
+ mediaManager.onSwipeToDismiss()
+ }
+
+ fun getCurrentVisibleMediaContentIntent(): PendingIntent? {
+ return MediaPlayerData.playerKeys()
+ .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+ ?.data
+ ?.clickIntent
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.apply {
+ println("keysNeedRemoval: $keysNeedRemoval")
+ println("dataKeys: ${MediaPlayerData.dataKeys()}")
+ println("orderedPlayerSortKeys: ${MediaPlayerData.playerKeys()}")
+ println("visiblePlayerSortKeys: ${MediaPlayerData.visiblePlayerKeys()}")
+ println("smartspaceMediaData: ${MediaPlayerData.smartspaceMediaData}")
+ println("shouldPrioritizeSs: ${MediaPlayerData.shouldPrioritizeSs}")
+ println("current size: $currentCarouselWidth x $currentCarouselHeight")
+ println("location: $desiredLocation")
+ println(
+ "state: ${desiredHostState?.expansion}, " +
+ "only active ${desiredHostState?.showsOnlyActiveMedia}"
+ )
+ }
+ }
+}
+
+@VisibleForTesting
+internal object MediaPlayerData {
+ private val EMPTY =
+ MediaData(
+ userId = -1,
+ initialized = false,
+ app = null,
+ appIcon = null,
+ artist = null,
+ song = null,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = "INVALID",
+ token = null,
+ clickIntent = null,
+ device = null,
+ active = true,
+ resumeAction = null,
+ instanceId = InstanceId.fakeInstanceId(-1),
+ appUid = -1
+ )
+ // Whether should prioritize Smartspace card.
+ internal var shouldPrioritizeSs: Boolean = false
+ private set
+ internal var smartspaceMediaData: SmartspaceMediaData? = null
+ private set
+
+ data class MediaSortKey(
+ val isSsMediaRec: Boolean, // Whether the item represents a Smartspace media recommendation.
+ val data: MediaData,
+ val key: String,
+ val updateTime: Long = 0,
+ val isSsReactivated: Boolean = false
+ )
+
+ private val comparator =
+ compareByDescending<MediaSortKey> {
+ it.data.isPlaying == true && it.data.playbackLocation == MediaData.PLAYBACK_LOCAL
+ }
+ .thenByDescending {
+ it.data.isPlaying == true &&
+ it.data.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL
+ }
+ .thenByDescending { it.data.active }
+ .thenByDescending { shouldPrioritizeSs == it.isSsMediaRec }
+ .thenByDescending { !it.data.resumption }
+ .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
+ .thenByDescending { it.data.lastActive }
+ .thenByDescending { it.updateTime }
+ .thenByDescending { it.data.notificationKey }
+
+ private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
+ private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
+ // A map that tracks order of visible media players before they get reordered.
+ private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>()
+
+ fun addMediaPlayer(
+ key: String,
+ data: MediaData,
+ player: MediaControlPanel,
+ clock: SystemClock,
+ isSsReactivated: Boolean,
+ debugLogger: MediaCarouselControllerLogger? = null
+ ) {
+ val removedPlayer = removeMediaPlayer(key)
+ if (removedPlayer != null && removedPlayer != player) {
+ debugLogger?.logPotentialMemoryLeak(key)
+ }
+ val sortKey =
+ MediaSortKey(
+ isSsMediaRec = false,
+ data,
+ key,
+ clock.currentTimeMillis(),
+ isSsReactivated = isSsReactivated
+ )
+ mediaData.put(key, sortKey)
+ mediaPlayers.put(sortKey, player)
+ visibleMediaPlayers.put(key, sortKey)
+ }
+
+ fun addMediaRecommendation(
+ key: String,
+ data: SmartspaceMediaData,
+ player: MediaControlPanel,
+ shouldPrioritize: Boolean,
+ clock: SystemClock,
+ debugLogger: MediaCarouselControllerLogger? = null
+ ) {
+ shouldPrioritizeSs = shouldPrioritize
+ val removedPlayer = removeMediaPlayer(key)
+ if (removedPlayer != null && removedPlayer != player) {
+ debugLogger?.logPotentialMemoryLeak(key)
+ }
+ val sortKey =
+ MediaSortKey(
+ isSsMediaRec = true,
+ EMPTY.copy(isPlaying = false),
+ key,
+ clock.currentTimeMillis(),
+ isSsReactivated = true
+ )
+ mediaData.put(key, sortKey)
+ mediaPlayers.put(sortKey, player)
+ visibleMediaPlayers.put(key, sortKey)
+ smartspaceMediaData = data
+ }
+
+ fun moveIfExists(
+ oldKey: String?,
+ newKey: String,
+ debugLogger: MediaCarouselControllerLogger? = null
+ ) {
+ if (oldKey == null || oldKey == newKey) {
+ return
+ }
+
+ mediaData.remove(oldKey)?.let {
+ // MediaPlayer should not be visible
+ // no need to set isDismissed flag.
+ val removedPlayer = removeMediaPlayer(newKey)
+ removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) }
+ mediaData.put(newKey, it)
+ }
+ }
+
+ fun getMediaControlPanel(visibleIndex: Int): MediaControlPanel? {
+ return mediaPlayers.get(visiblePlayerKeys().elementAt(visibleIndex))
+ }
+
+ fun getMediaPlayer(key: String): MediaControlPanel? {
+ return mediaData.get(key)?.let { mediaPlayers.get(it) }
+ }
+
+ fun getMediaPlayerIndex(key: String): Int {
+ val sortKey = mediaData.get(key)
+ mediaPlayers.entries.forEachIndexed { index, e ->
+ if (e.key == sortKey) {
+ return index
+ }
+ }
+ return -1
+ }
+
+ /**
+ * Removes media player given the key.
+ * @param isDismissed determines whether the media player is removed from the carousel.
+ */
+ fun removeMediaPlayer(key: String, isDismissed: Boolean = false) =
+ mediaData.remove(key)?.let {
+ if (it.isSsMediaRec) {
+ smartspaceMediaData = null
+ }
+ if (isDismissed) {
+ visibleMediaPlayers.remove(key)
+ }
+ mediaPlayers.remove(it)
+ }
+
+ fun mediaData() =
+ mediaData.entries.map { e -> Triple(e.key, e.value.data, e.value.isSsMediaRec) }
+
+ fun dataKeys() = mediaData.keys
+
+ fun players() = mediaPlayers.values
+
+ fun playerKeys() = mediaPlayers.keys
+
+ fun visiblePlayerKeys() = visibleMediaPlayers.values
+
+ /** Returns the index of the first non-timeout media. */
+ fun firstActiveMediaIndex(): Int {
+ mediaPlayers.entries.forEachIndexed { index, e ->
+ if (!e.key.isSsMediaRec && e.key.data.active) {
+ return index
+ }
+ }
+ return -1
+ }
+
+ /** Returns the existing Smartspace target id. */
+ fun smartspaceMediaKey(): String? {
+ mediaData.entries.forEach { e ->
+ if (e.value.isSsMediaRec) {
+ return e.key
+ }
+ }
+ return null
+ }
+
+ @VisibleForTesting
+ fun clear() {
+ mediaData.clear()
+ mediaPlayers.clear()
+ visibleMediaPlayers.clear()
+ }
+
+ /* Returns true if there is active media player card or recommendation card */
+ fun hasActiveMediaOrRecommendationCard(): Boolean {
+ if (smartspaceMediaData != null && smartspaceMediaData?.isActive!!) {
+ return true
+ }
+ if (firstActiveMediaIndex() != -1) {
+ return true
+ }
+ return false
+ }
+
+ fun isSsReactivated(key: String): Boolean = mediaData.get(key)?.isSsReactivated ?: false
+
+ /**
+ * This method is called when media players are reordered. To make sure we have the new version
+ * of the order of media players visible to user.
+ */
+ fun updateVisibleMediaPlayers() {
+ visibleMediaPlayers.clear()
+ playerKeys().forEach { visibleMediaPlayers.put(it.key, it) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
index b1018f9544c0..eed1bd743938 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
@@ -14,63 +14,53 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaCarouselControllerLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** A debug logger for [MediaCarouselController]. */
@SysUISingleton
-class MediaCarouselControllerLogger @Inject constructor(
- @MediaCarouselControllerLog private val buffer: LogBuffer
-) {
+class MediaCarouselControllerLogger
+@Inject
+constructor(@MediaCarouselControllerLog private val buffer: LogBuffer) {
/**
* Log that there might be a potential memory leak for the [MediaControlPanel] and/or
* [MediaViewController] related to [key].
*/
- fun logPotentialMemoryLeak(key: String) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = key },
- {
- "Potential memory leak: " +
+ fun logPotentialMemoryLeak(key: String) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = key },
+ {
+ "Potential memory leak: " +
"Removing control panel for $str1 from map without calling #onDestroy"
- }
- )
+ }
+ )
- fun logMediaLoaded(key: String) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = key },
- { "add player $str1" }
- )
+ fun logMediaLoaded(key: String) =
+ buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "add player $str1" })
- fun logMediaRemoved(key: String) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = key },
- { "removing player $str1" }
- )
+ fun logMediaRemoved(key: String) =
+ buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" })
- fun logRecommendationLoaded(key: String) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = key },
- { "add recommendation $str1" }
- )
+ fun logRecommendationLoaded(key: String) =
+ buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "add recommendation $str1" })
- fun logRecommendationRemoved(key: String, immediately: Boolean) = buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = key
- bool1 = immediately
- },
- { "removing recommendation $str1, immediate=$bool1" }
- )
+ fun logRecommendationRemoved(key: String, immediately: Boolean) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ bool1 = immediately
+ },
+ { "removing recommendation $str1, immediate=$bool1" }
+ )
}
private const val TAG = "MediaCarouselCtlrLog"
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index a776897b2fd5..36b2eda65fab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.graphics.Outline
import android.util.MathUtils
@@ -31,6 +31,7 @@ import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -43,16 +44,13 @@ private const val RUBBERBAND_FACTOR = 0.2f
private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
/**
- * Default spring configuration to use for animations where stiffness and/or damping ratio
- * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
+ * Default spring configuration to use for animations where stiffness and/or damping ratio were not
+ * provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
*/
-private val translationConfig = PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_LOW,
- SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+private val translationConfig =
+ PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY)
-/**
- * A controller class for the media scrollview, responsible for touch handling
- */
+/** A controller class for the media scrollview, responsible for touch handling */
class MediaCarouselScrollHandler(
private val scrollView: MediaScrollView,
private val pageIndicator: PageIndicator,
@@ -65,57 +63,36 @@ class MediaCarouselScrollHandler(
private val logSmartspaceImpression: (Boolean) -> Unit,
private val logger: MediaUiEventLogger
) {
- /**
- * Is the view in RTL
- */
- val isRtl: Boolean get() = scrollView.isLayoutRtl
- /**
- * Do we need falsing protection?
- */
+ /** Is the view in RTL */
+ val isRtl: Boolean
+ get() = scrollView.isLayoutRtl
+ /** Do we need falsing protection? */
var falsingProtectionNeeded: Boolean = false
- /**
- * The width of the carousel
- */
+ /** The width of the carousel */
private var carouselWidth: Int = 0
- /**
- * The height of the carousel
- */
+ /** The height of the carousel */
private var carouselHeight: Int = 0
- /**
- * How much are we scrolled into the current media?
- */
+ /** How much are we scrolled into the current media? */
private var cornerRadius: Int = 0
- /**
- * The content where the players are added
- */
+ /** The content where the players are added */
private var mediaContent: ViewGroup
- /**
- * The gesture detector to detect touch gestures
- */
+ /** The gesture detector to detect touch gestures */
private val gestureDetector: GestureDetectorCompat
- /**
- * The settings button view
- */
+ /** The settings button view */
private lateinit var settingsButton: View
- /**
- * What's the currently visible player index?
- */
+ /** What's the currently visible player index? */
var visibleMediaIndex: Int = 0
private set
- /**
- * How much are we scrolled into the current media?
- */
+ /** How much are we scrolled into the current media? */
private var scrollIntoCurrentMedia: Int = 0
- /**
- * how much is the content translated in X
- */
+ /** how much is the content translated in X */
var contentTranslation = 0.0f
private set(value) {
field = value
@@ -125,9 +102,7 @@ class MediaCarouselScrollHandler(
updateClipToOutline()
}
- /**
- * The width of a player including padding
- */
+ /** The width of a player including padding */
var playerWidthPlusPadding: Int = 0
set(value) {
field = value
@@ -135,82 +110,75 @@ class MediaCarouselScrollHandler(
// it's still at the same place
var newRelativeScroll = visibleMediaIndex * playerWidthPlusPadding
if (scrollIntoCurrentMedia > playerWidthPlusPadding) {
- newRelativeScroll += playerWidthPlusPadding -
- (scrollIntoCurrentMedia - playerWidthPlusPadding)
+ newRelativeScroll +=
+ playerWidthPlusPadding - (scrollIntoCurrentMedia - playerWidthPlusPadding)
} else {
newRelativeScroll += scrollIntoCurrentMedia
}
scrollView.relativeScrollX = newRelativeScroll
}
- /**
- * Does the dismiss currently show the setting cog?
- */
+ /** Does the dismiss currently show the setting cog? */
var showsSettingsButton: Boolean = false
- /**
- * A utility to detect gestures, used in the touch listener
- */
- private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
- override fun onFling(
- eStart: MotionEvent?,
- eCurrent: MotionEvent?,
- vX: Float,
- vY: Float
- ) = onFling(vX, vY)
-
- override fun onScroll(
- down: MotionEvent?,
- lastMotion: MotionEvent?,
- distanceX: Float,
- distanceY: Float
- ) = onScroll(down!!, lastMotion!!, distanceX)
-
- override fun onDown(e: MotionEvent?): Boolean {
- if (falsingProtectionNeeded) {
- falsingCollector.onNotificationStartDismissing()
+ /** A utility to detect gestures, used in the touch listener */
+ private val gestureListener =
+ object : GestureDetector.SimpleOnGestureListener() {
+ override fun onFling(
+ eStart: MotionEvent?,
+ eCurrent: MotionEvent?,
+ vX: Float,
+ vY: Float
+ ) = onFling(vX, vY)
+
+ override fun onScroll(
+ down: MotionEvent?,
+ lastMotion: MotionEvent?,
+ distanceX: Float,
+ distanceY: Float
+ ) = onScroll(down!!, lastMotion!!, distanceX)
+
+ override fun onDown(e: MotionEvent?): Boolean {
+ if (falsingProtectionNeeded) {
+ falsingCollector.onNotificationStartDismissing()
+ }
+ return false
}
- return false
}
- }
- /**
- * The touch listener for the scroll view
- */
- private val touchListener = object : Gefingerpoken {
- override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!)
- override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!)
- }
+ /** The touch listener for the scroll view */
+ private val touchListener =
+ object : Gefingerpoken {
+ override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!)
+ override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!)
+ }
- /**
- * A listener that is invoked when the scrolling changes to update player visibilities
- */
- private val scrollChangedListener = object : View.OnScrollChangeListener {
- override fun onScrollChange(
- v: View?,
- scrollX: Int,
- scrollY: Int,
- oldScrollX: Int,
- oldScrollY: Int
- ) {
- if (playerWidthPlusPadding == 0) {
- return
- }
+ /** A listener that is invoked when the scrolling changes to update player visibilities */
+ private val scrollChangedListener =
+ object : View.OnScrollChangeListener {
+ override fun onScrollChange(
+ v: View?,
+ scrollX: Int,
+ scrollY: Int,
+ oldScrollX: Int,
+ oldScrollY: Int
+ ) {
+ if (playerWidthPlusPadding == 0) {
+ return
+ }
- val relativeScrollX = scrollView.relativeScrollX
- onMediaScrollingChanged(relativeScrollX / playerWidthPlusPadding,
- relativeScrollX % playerWidthPlusPadding)
+ val relativeScrollX = scrollView.relativeScrollX
+ onMediaScrollingChanged(
+ relativeScrollX / playerWidthPlusPadding,
+ relativeScrollX % playerWidthPlusPadding
+ )
+ }
}
- }
- /**
- * Whether the media card is visible to user if any
- */
+ /** Whether the media card is visible to user if any */
var visibleToUser: Boolean = false
- /**
- * Whether the quick setting is expanded or not
- */
+ /** Whether the quick setting is expanded or not */
var qsExpanded: Boolean = false
init {
@@ -219,47 +187,61 @@ class MediaCarouselScrollHandler(
scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER)
mediaContent = scrollView.contentContainer
scrollView.setOnScrollChangeListener(scrollChangedListener)
- scrollView.outlineProvider = object : ViewOutlineProvider() {
- override fun getOutline(view: View?, outline: Outline?) {
- outline?.setRoundRect(0, 0, carouselWidth, carouselHeight, cornerRadius.toFloat())
+ scrollView.outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View?, outline: Outline?) {
+ outline?.setRoundRect(
+ 0,
+ 0,
+ carouselWidth,
+ carouselHeight,
+ cornerRadius.toFloat()
+ )
+ }
}
- }
}
fun onSettingsButtonUpdated(button: View) {
settingsButton = button
// We don't have a context to resolve, lets use the settingsbuttons one since that is
// reinflated appropriately
- cornerRadius = settingsButton.resources.getDimensionPixelSize(
- Utils.getThemeAttr(settingsButton.context, android.R.attr.dialogCornerRadius))
+ cornerRadius =
+ settingsButton.resources.getDimensionPixelSize(
+ Utils.getThemeAttr(settingsButton.context, android.R.attr.dialogCornerRadius)
+ )
updateSettingsPresentation()
scrollView.invalidateOutline()
}
private fun updateSettingsPresentation() {
if (showsSettingsButton && settingsButton.width > 0) {
- val settingsOffset = MathUtils.map(
+ val settingsOffset =
+ MathUtils.map(
0.0f,
getMaxTranslation().toFloat(),
0.0f,
1.0f,
- Math.abs(contentTranslation))
- val settingsTranslation = (1.0f - settingsOffset) * -settingsButton.width *
+ Math.abs(contentTranslation)
+ )
+ val settingsTranslation =
+ (1.0f - settingsOffset) *
+ -settingsButton.width *
SETTINGS_BUTTON_TRANSLATION_FRACTION
- val newTranslationX = if (isRtl) {
- // In RTL, the 0-placement is on the right side of the view, not the left...
- if (contentTranslation > 0) {
- -(scrollView.width - settingsTranslation - settingsButton.width)
- } else {
- -settingsTranslation
- }
- } else {
- if (contentTranslation > 0) {
- settingsTranslation
+ val newTranslationX =
+ if (isRtl) {
+ // In RTL, the 0-placement is on the right side of the view, not the left...
+ if (contentTranslation > 0) {
+ -(scrollView.width - settingsTranslation - settingsButton.width)
+ } else {
+ -settingsTranslation
+ }
} else {
- scrollView.width - settingsTranslation - settingsButton.width
+ if (contentTranslation > 0) {
+ settingsTranslation
+ } else {
+ scrollView.width - settingsTranslation - settingsButton.width
+ }
}
- }
val rotation = (1.0f - settingsOffset) * 50
settingsButton.rotation = rotation * -Math.signum(contentTranslation)
val alpha = MathUtils.saturate(MathUtils.map(0.5f, 1.0f, 0.0f, 1.0f, settingsOffset))
@@ -306,16 +288,14 @@ class MediaCarouselScrollHandler(
val newScrollX = scrollView.relativeScrollX + dx
// Delay the scrolling since scrollView calls springback which cancels
// the animation again..
- mainExecutor.execute {
- scrollView.smoothScrollTo(newScrollX, scrollView.scrollY)
- }
+ mainExecutor.execute { scrollView.smoothScrollTo(newScrollX, scrollView.scrollY) }
}
val currentTranslation = scrollView.getContentTranslation()
if (currentTranslation != 0.0f) {
// We started a Swipe but didn't end up with a fling. Let's either go to the
// dismissed position or go back.
- val springBack = Math.abs(currentTranslation) < getMaxTranslation() / 2 ||
- isFalseTouch()
+ val springBack =
+ Math.abs(currentTranslation) < getMaxTranslation() / 2 || isFalseTouch()
val newTranslation: Float
if (springBack) {
newTranslation = 0.0f
@@ -324,13 +304,17 @@ class MediaCarouselScrollHandler(
if (!showsSettingsButton) {
// Delay the dismiss a bit to avoid too much overlap. Waiting until the
// animation has finished also feels a bit too slow here.
- mainExecutor.executeDelayed({
- dismissCallback.invoke()
- }, DISMISS_DELAY)
+ mainExecutor.executeDelayed({ dismissCallback.invoke() }, DISMISS_DELAY)
}
}
- PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION,
- newTranslation, startVelocity = 0.0f, config = translationConfig).start()
+ PhysicsAnimator.getInstance(this)
+ .spring(
+ CONTENT_TRANSLATION,
+ newTranslation,
+ startVelocity = 0.0f,
+ config = translationConfig
+ )
+ .start()
scrollView.animationTargetX = newTranslation
}
}
@@ -338,10 +322,11 @@ class MediaCarouselScrollHandler(
return false
}
- private fun isFalseTouch() = falsingProtectionNeeded &&
- falsingManager.isFalseTouch(NOTIFICATION_DISMISS)
+ private fun isFalseTouch() =
+ falsingProtectionNeeded && falsingManager.isFalseTouch(NOTIFICATION_DISMISS)
- private fun getMaxTranslation() = if (showsSettingsButton) {
+ private fun getMaxTranslation() =
+ if (showsSettingsButton) {
settingsButton.width
} else {
playerWidthPlusPadding
@@ -351,15 +336,10 @@ class MediaCarouselScrollHandler(
return gestureDetector.onTouchEvent(motionEvent)
}
- fun onScroll(
- down: MotionEvent,
- lastMotion: MotionEvent,
- distanceX: Float
- ): Boolean {
+ fun onScroll(down: MotionEvent, lastMotion: MotionEvent, distanceX: Float): Boolean {
val totalX = lastMotion.x - down.x
val currentTranslation = scrollView.getContentTranslation()
- if (currentTranslation != 0.0f ||
- !scrollView.canScrollHorizontally((-totalX).toInt())) {
+ if (currentTranslation != 0.0f || !scrollView.canScrollHorizontally((-totalX).toInt())) {
var newTranslation = currentTranslation - distanceX
val absTranslation = Math.abs(newTranslation)
if (absTranslation > getMaxTranslation()) {
@@ -373,14 +353,18 @@ class MediaCarouselScrollHandler(
newTranslation = currentTranslation - distanceX * RUBBERBAND_FACTOR
} else {
// We just crossed the boundary, let's rubberband it all
- newTranslation = Math.signum(newTranslation) * (getMaxTranslation() +
- (absTranslation - getMaxTranslation()) * RUBBERBAND_FACTOR)
+ newTranslation =
+ Math.signum(newTranslation) *
+ (getMaxTranslation() +
+ (absTranslation - getMaxTranslation()) * RUBBERBAND_FACTOR)
}
} // Otherwise we don't have do do anything, and will remove the unrubberbanded
// translation
}
- if (Math.signum(newTranslation) != Math.signum(currentTranslation) &&
- currentTranslation != 0.0f) {
+ if (
+ Math.signum(newTranslation) != Math.signum(currentTranslation) &&
+ currentTranslation != 0.0f
+ ) {
// We crossed the 0.0 threshold of the translation. Let's see if we're allowed
// to scroll into the new direction
if (scrollView.canScrollHorizontally(-newTranslation.toInt())) {
@@ -391,8 +375,14 @@ class MediaCarouselScrollHandler(
}
val physicsAnimator = PhysicsAnimator.getInstance(this)
if (physicsAnimator.isRunning()) {
- physicsAnimator.spring(CONTENT_TRANSLATION,
- newTranslation, startVelocity = 0.0f, config = translationConfig).start()
+ physicsAnimator
+ .spring(
+ CONTENT_TRANSLATION,
+ newTranslation,
+ startVelocity = 0.0f,
+ config = translationConfig
+ )
+ .start()
} else {
contentTranslation = newTranslation
}
@@ -402,10 +392,7 @@ class MediaCarouselScrollHandler(
return false
}
- private fun onFling(
- vX: Float,
- vY: Float
- ): Boolean {
+ private fun onFling(vX: Float, vY: Float): Boolean {
if (vX * vX < 0.5 * vY * vY) {
return false
}
@@ -424,13 +411,17 @@ class MediaCarouselScrollHandler(
// Delay the dismiss a bit to avoid too much overlap. Waiting until the animation
// has finished also feels a bit too slow here.
if (!showsSettingsButton) {
- mainExecutor.executeDelayed({
- dismissCallback.invoke()
- }, DISMISS_DELAY)
+ mainExecutor.executeDelayed({ dismissCallback.invoke() }, DISMISS_DELAY)
}
}
- PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION,
- newTranslation, startVelocity = vX, config = translationConfig).start()
+ PhysicsAnimator.getInstance(this)
+ .spring(
+ CONTENT_TRANSLATION,
+ newTranslation,
+ startVelocity = vX,
+ config = translationConfig
+ )
+ .start()
scrollView.animationTargetX = newTranslation
} else {
// We're flinging the player! Let's go either to the previous or to the next player
@@ -443,21 +434,18 @@ class MediaCarouselScrollHandler(
val view = mediaContent.getChildAt(destIndex)
// We need to post this since we're dispatching a touch to the underlying view to cancel
// but canceling will actually abort the animation.
- mainExecutor.execute {
- scrollView.smoothScrollTo(view.left, scrollView.scrollY)
- }
+ mainExecutor.execute { scrollView.smoothScrollTo(view.left, scrollView.scrollY) }
}
return true
}
- /**
- * Reset the translation of the players when swiped
- */
+ /** Reset the translation of the players when swiped */
fun resetTranslation(animate: Boolean = false) {
if (scrollView.getContentTranslation() != 0.0f) {
if (animate) {
- PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION,
- 0.0f, config = translationConfig).start()
+ PhysicsAnimator.getInstance(this)
+ .spring(CONTENT_TRANSLATION, 0.0f, config = translationConfig)
+ .start()
scrollView.animationTargetX = 0.0f
} else {
PhysicsAnimator.getInstance(this).cancel()
@@ -485,21 +473,22 @@ class MediaCarouselScrollHandler(
closeGuts(false)
updatePlayerVisibilities()
}
- val relativeLocation = visibleMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
- scrollInAmount.toFloat() / playerWidthPlusPadding else 0f
+ val relativeLocation =
+ visibleMediaIndex.toFloat() +
+ if (playerWidthPlusPadding > 0) scrollInAmount.toFloat() / playerWidthPlusPadding
+ else 0f
// Fix the location, because PageIndicator does not handle RTL internally
- val location = if (isRtl) {
- mediaContent.childCount - relativeLocation - 1
- } else {
- relativeLocation
- }
+ val location =
+ if (isRtl) {
+ mediaContent.childCount - relativeLocation - 1
+ } else {
+ relativeLocation
+ }
pageIndicator.setLocation(location)
updateClipToOutline()
}
- /**
- * Notified whenever the players or their order has changed
- */
+ /** Notified whenever the players or their order has changed */
fun onPlayersChanged() {
updatePlayerVisibilities()
updateMediaPaddings()
@@ -529,8 +518,8 @@ class MediaCarouselScrollHandler(
}
/**
- * Notify that a player will be removed right away. This gives us the opporunity to look
- * where it was and update our scroll position.
+ * Notify that a player will be removed right away. This gives us the opporunity to look where
+ * it was and update our scroll position.
*/
fun onPrePlayerRemoved(removed: MediaControlPanel) {
val removedIndex = mediaContent.indexOfChild(removed.mediaViewHolder?.player)
@@ -550,9 +539,7 @@ class MediaCarouselScrollHandler(
}
}
- /**
- * Update the bounds of the carousel
- */
+ /** Update the bounds of the carousel */
fun setCarouselBounds(currentCarouselWidth: Int, currentCarouselHeight: Int) {
if (currentCarouselHeight != carouselHeight || currentCarouselWidth != carouselHeight) {
carouselWidth = currentCarouselWidth
@@ -561,9 +548,7 @@ class MediaCarouselScrollHandler(
}
}
- /**
- * Reset the MediaScrollView to the start.
- */
+ /** Reset the MediaScrollView to the start. */
fun scrollToStart() {
scrollView.relativeScrollX = 0
}
@@ -581,21 +566,22 @@ class MediaCarouselScrollHandler(
val destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex)
val view = mediaContent.getChildAt(destIndex)
// We need to post this to wait for the active player becomes visible.
- mainExecutor.executeDelayed({
- scrollView.smoothScrollTo(view.left, scrollView.scrollY)
- }, SCROLL_DELAY)
+ mainExecutor.executeDelayed(
+ { scrollView.smoothScrollTo(view.left, scrollView.scrollY) },
+ SCROLL_DELAY
+ )
}
companion object {
- private val CONTENT_TRANSLATION = object : FloatPropertyCompat<MediaCarouselScrollHandler>(
- "contentTranslation") {
- override fun getValue(handler: MediaCarouselScrollHandler): Float {
- return handler.contentTranslation
- }
+ private val CONTENT_TRANSLATION =
+ object : FloatPropertyCompat<MediaCarouselScrollHandler>("contentTranslation") {
+ override fun getValue(handler: MediaCarouselScrollHandler): Float {
+ return handler.contentTranslation
+ }
- override fun setValue(handler: MediaCarouselScrollHandler, value: Float) {
- handler.contentTranslation = value
+ override fun setValue(handler: MediaCarouselScrollHandler, value: Float) {
+ handler.contentTranslation = value
+ }
}
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
index 208766d7f5e6..82abf9bfcc80 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaColorSchemes.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import com.android.systemui.monet.ColorScheme
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 759795f84963..18ecadb28cf3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.media;
+package com.android.systemui.media.controls.ui;
import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
-import static com.android.systemui.media.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
+import static com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
import android.animation.Animator;
import android.animation.AnimatorInflater;
@@ -76,6 +76,20 @@ 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;
+import com.android.systemui.media.controls.models.GutsViewHolder;
+import com.android.systemui.media.controls.models.player.MediaAction;
+import com.android.systemui.media.controls.models.player.MediaButton;
+import com.android.systemui.media.controls.models.player.MediaData;
+import com.android.systemui.media.controls.models.player.MediaDeviceData;
+import com.android.systemui.media.controls.models.player.MediaViewHolder;
+import com.android.systemui.media.controls.models.player.SeekBarObserver;
+import com.android.systemui.media.controls.models.player.SeekBarViewModel;
+import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder;
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.util.MediaDataUtils;
+import com.android.systemui.media.controls.util.MediaUiEventLogger;
+import com.android.systemui.media.controls.util.SmallHash;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.monet.ColorScheme;
import com.android.systemui.monet.Style;
@@ -111,7 +125,6 @@ public class MediaControlPanel {
"com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name";
private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
- protected static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME";
// Event types logged by smartspace
private static final int SMARTSPACE_CARD_CLICK_EVENT = 760;
@@ -251,6 +264,9 @@ public class MediaControlPanel {
});
}
+ /**
+ * Clean up seekbar and controller when panel is destroyed
+ */
public void onDestroy() {
if (mSeekBarObserver != null) {
mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver);
@@ -651,7 +667,7 @@ public class MediaControlPanel {
return;
}
- CharSequence contentDescription;
+ CharSequence contentDescription;
if (mMediaViewController.isGutsVisible()) {
contentDescription =
mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
@@ -1441,7 +1457,7 @@ public class MediaControlPanel {
}
// Automatically scroll to the active player once the media is loaded.
- mMediaCarouselController.setShouldScrollToActivePlayer(true);
+ mMediaCarouselController.setShouldScrollToKey(true);
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index e0b6d1f9de7b..6b46d8f30cad 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -59,9 +59,7 @@ import javax.inject.Inject
private val TAG: String = MediaHierarchyManager::class.java.simpleName
-/**
- * Similarly to isShown but also excludes views that have 0 alpha
- */
+/** Similarly to isShown but also excludes views that have 0 alpha */
val View.isShownNotFaded: Boolean
get() {
var current: View = this
@@ -86,7 +84,9 @@ val View.isShownNotFaded: Boolean
* and animate the positions of the views to achieve seamless transitions.
*/
@SysUISingleton
-class MediaHierarchyManager @Inject constructor(
+class MediaHierarchyManager
+@Inject
+constructor(
private val context: Context,
private val statusBarStateController: SysuiStatusBarStateController,
private val keyguardStateController: KeyguardStateController,
@@ -101,12 +101,10 @@ class MediaHierarchyManager @Inject constructor(
@Main private val handler: Handler,
) {
- /**
- * Track the media player setting status on lock screen.
- */
+ /** Track the media player setting status on lock screen. */
private var allowMediaPlayerOnLockScreen: Boolean = true
private val lockScreenMediaPlayerUri =
- secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
+ secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
/**
* Whether we "skip" QQS during panel expansion.
@@ -118,8 +116,8 @@ class MediaHierarchyManager @Inject constructor(
/**
* The root overlay of the hierarchy. This is where the media notification is attached to
- * whenever the view is transitioning from one host to another. It also make sure that the
- * view is always in its final state when it is attached to a view host.
+ * whenever the view is transitioning from one host to another. It also make sure that the view
+ * is always in its final state when it is attached to a view host.
*/
private var rootOverlay: ViewGroupOverlay? = null
@@ -138,69 +136,68 @@ class MediaHierarchyManager @Inject constructor(
*/
private var animationStartCrossFadeProgress = 0.0f
- /**
- * The starting alpha of the animation
- */
+ /** The starting alpha of the animation */
private var animationStartAlpha = 0.0f
- /**
- * The starting location of the cross fade if an animation is running right now.
- */
- @MediaLocation
- private var crossFadeAnimationStartLocation = -1
+ /** The starting location of the cross fade if an animation is running right now. */
+ @MediaLocation private var crossFadeAnimationStartLocation = -1
- /**
- * The end location of the cross fade if an animation is running right now.
- */
- @MediaLocation
- private var crossFadeAnimationEndLocation = -1
+ /** The end location of the cross fade if an animation is running right now. */
+ @MediaLocation private var crossFadeAnimationEndLocation = -1
private var targetBounds: Rect = Rect()
private val mediaFrame
get() = mediaCarouselController.mediaFrame
private var statusbarState: Int = statusBarStateController.state
- private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply {
- interpolator = Interpolators.FAST_OUT_SLOW_IN
- addUpdateListener {
- updateTargetState()
- val currentAlpha: Float
- var boundsProgress = animatedFraction
- if (isCrossFadeAnimatorRunning) {
- animationCrossFadeProgress = MathUtils.lerp(animationStartCrossFadeProgress, 1.0f,
- 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)
- } else {
- // If we're not crossfading, let's interpolate from the start alpha to 1.0f
- currentAlpha = MathUtils.lerp(animationStartAlpha, 1.0f, animatedFraction)
+ private var animator =
+ ValueAnimator.ofFloat(0.0f, 1.0f).apply {
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ addUpdateListener {
+ updateTargetState()
+ val currentAlpha: Float
+ var boundsProgress = animatedFraction
+ if (isCrossFadeAnimatorRunning) {
+ animationCrossFadeProgress =
+ MathUtils.lerp(animationStartCrossFadeProgress, 1.0f, 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)
+ } else {
+ // If we're not crossfading, let's interpolate from the start alpha to 1.0f
+ currentAlpha = MathUtils.lerp(animationStartAlpha, 1.0f, animatedFraction)
+ }
+ interpolateBounds(
+ animationStartBounds,
+ targetBounds,
+ boundsProgress,
+ result = currentBounds
+ )
+ resolveClipping(currentClipping)
+ applyState(currentBounds, currentAlpha, clipBounds = currentClipping)
}
- interpolateBounds(animationStartBounds, targetBounds, boundsProgress,
- result = currentBounds)
- resolveClipping(currentClipping)
- applyState(currentBounds, currentAlpha, clipBounds = currentClipping)
- }
- addListener(object : AnimatorListenerAdapter() {
- private var cancelled: Boolean = false
+ addListener(
+ object : AnimatorListenerAdapter() {
+ private var cancelled: Boolean = false
+
+ override fun onAnimationCancel(animation: Animator?) {
+ cancelled = true
+ animationPending = false
+ rootView?.removeCallbacks(startAnimation)
+ }
- override fun onAnimationCancel(animation: Animator?) {
- cancelled = true
- animationPending = false
- rootView?.removeCallbacks(startAnimation)
- }
+ override fun onAnimationEnd(animation: Animator?) {
+ isCrossFadeAnimatorRunning = false
+ if (!cancelled) {
+ applyTargetStateIfNotAnimating()
+ }
+ }
- override fun onAnimationEnd(animation: Animator?) {
- isCrossFadeAnimatorRunning = false
- if (!cancelled) {
- applyTargetStateIfNotAnimating()
+ override fun onAnimationStart(animation: Animator?) {
+ cancelled = false
+ animationPending = false
+ }
}
- }
-
- override fun onAnimationStart(animation: Animator?) {
- cancelled = false
- animationPending = false
- }
- })
- }
+ )
+ }
private fun resolveClipping(result: Rect) {
if (animationStartClipping.isEmpty) result.set(targetClipping)
@@ -210,42 +207,31 @@ class MediaHierarchyManager @Inject constructor(
private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1)
/**
- * The last location where this view was at before going to the desired location. This is
- * useful for guided transitions.
+ * The last location where this view was at before going to the desired location. This is useful
+ * for guided transitions.
*/
- @MediaLocation
- private var previousLocation = -1
- /**
- * The desired location where the view will be at the end of the transition.
- */
- @MediaLocation
- private var desiredLocation = -1
+ @MediaLocation private var previousLocation = -1
+ /** The desired location where the view will be at the end of the transition. */
+ @MediaLocation private var desiredLocation = -1
/**
- * The current attachment location where the view is currently attached.
- * Usually this matches the desired location except for animations whenever a view moves
- * to the new desired location, during which it is in [IN_OVERLAY].
+ * The current attachment location where the view is currently attached. Usually this matches
+ * the desired location except for animations whenever a view moves to the new desired location,
+ * during which it is in [IN_OVERLAY].
*/
- @MediaLocation
- private var currentAttachmentLocation = -1
+ @MediaLocation private var currentAttachmentLocation = -1
private var inSplitShade = false
- /**
- * Is there any active media in the carousel?
- */
+ /** Is there any active media in the carousel? */
private var hasActiveMedia: Boolean = false
get() = mediaHosts.get(LOCATION_QQS)?.visible == true
- /**
- * Are we currently waiting on an animation to start?
- */
+ /** Are we currently waiting on an animation to start? */
private var animationPending: Boolean = false
private val startAnimation: Runnable = Runnable { animator.start() }
- /**
- * The expansion of quick settings
- */
+ /** The expansion of quick settings */
var qsExpansion: Float = 0.0f
set(value) {
if (field != value) {
@@ -258,9 +244,7 @@ class MediaHierarchyManager @Inject constructor(
}
}
- /**
- * Is quick setting expanded?
- */
+ /** Is quick setting expanded? */
var qsExpanded: Boolean = false
set(value) {
if (field != value) {
@@ -281,9 +265,8 @@ class MediaHierarchyManager @Inject constructor(
private var distanceForFullShadeTransition = 0
/**
- * The amount of progress we are currently in if we're transitioning to the full shade.
- * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
- * shade.
+ * The amount of progress we are currently in if we're transitioning to the full shade. 0.0f
+ * means we're not transitioning yet, while 1 means we're all the way in the full shade.
*/
private var fullShadeTransitionProgress = 0f
set(value) {
@@ -305,9 +288,7 @@ class MediaHierarchyManager @Inject constructor(
}
}
- /**
- * Is there currently a cross-fade animation running driven by an animator?
- */
+ /** Is there currently a cross-fade animation running driven by an animator? */
private var isCrossFadeAnimatorRunning = false
/**
@@ -316,8 +297,10 @@ class MediaHierarchyManager @Inject constructor(
* the transition starts, this will no longer return true.
*/
private val isTransitioningToFullShade: Boolean
- get() = fullShadeTransitionProgress != 0f && !bypassController.bypassEnabled &&
- statusbarState == StatusBarState.KEYGUARD
+ get() =
+ fullShadeTransitionProgress != 0f &&
+ !bypassController.bypassEnabled &&
+ statusbarState == StatusBarState.KEYGUARD
/**
* Set the amount of pixels we have currently dragged down if we're transitioning to the full
@@ -354,17 +337,13 @@ class MediaHierarchyManager @Inject constructor(
}
}
- /**
- * Are location changes currently blocked?
- */
+ /** Are location changes currently blocked? */
private val blockLocationChanges: Boolean
get() {
return goingToSleep || dozeAnimationRunning
}
- /**
- * Are we currently going to sleep
- */
+ /** Are we currently going to sleep */
private var goingToSleep: Boolean = false
set(value) {
if (field != value) {
@@ -375,9 +354,7 @@ class MediaHierarchyManager @Inject constructor(
}
}
- /**
- * Are we currently fullyAwake
- */
+ /** Are we currently fullyAwake */
private var fullyAwake: Boolean = false
set(value) {
if (field != value) {
@@ -388,9 +365,7 @@ class MediaHierarchyManager @Inject constructor(
}
}
- /**
- * Is the doze animation currently Running
- */
+ /** Is the doze animation currently Running */
private var dozeAnimationRunning: Boolean = false
private set(value) {
if (field != value) {
@@ -401,9 +376,7 @@ class MediaHierarchyManager @Inject constructor(
}
}
- /**
- * Is the dream overlay currently active
- */
+ /** Is the dream overlay currently active */
private var dreamOverlayActive: Boolean = false
private set(value) {
if (field != value) {
@@ -412,9 +385,7 @@ class MediaHierarchyManager @Inject constructor(
}
}
- /**
- * Is the dream media complication currently active
- */
+ /** Is the dream media complication currently active */
private var dreamMediaComplicationActive: Boolean = false
private set(value) {
if (field != value) {
@@ -424,16 +395,13 @@ class MediaHierarchyManager @Inject constructor(
}
/**
- * 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.
- * This is only valid while [isCrossFadeAnimatorRunning] is true.
+ * 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. This is only valid while [isCrossFadeAnimatorRunning] is true.
*/
private var animationCrossFadeProgress = 1.0f
- /**
- * The current carousel Alpha.
- */
+ /** The current carousel Alpha. */
private var carouselAlpha: Float = 1.0f
set(value) {
if (field == value) {
@@ -447,8 +415,8 @@ class MediaHierarchyManager @Inject constructor(
* Calculate the alpha of the view when given a cross-fade progress.
*
* @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.
+ * 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.
*/
private fun calculateAlphaFromCrossFade(crossFadeProgress: Float): Float {
if (crossFadeProgress <= 0.5f) {
@@ -460,132 +428,152 @@ class MediaHierarchyManager @Inject constructor(
init {
updateConfiguration()
- configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- updateConfiguration()
- updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = true)
- }
- })
- statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
- override fun onStatePreChange(oldState: Int, newState: Int) {
- // We're updating the location before the state change happens, since we want the
- // location of the previous state to still be up to date when the animation starts
- statusbarState = newState
- updateDesiredLocation()
- }
-
- override fun onStateChanged(newState: Int) {
- updateTargetState()
- // Enters shade from lock screen
- if (newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()) {
- mediaCarouselController.logSmartspaceImpression(qsExpanded)
+ configurationController.addCallback(
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ updateConfiguration()
+ updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = true)
}
- mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
- }
-
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- dozeAnimationRunning = linear != 0.0f && linear != 1.0f
}
+ )
+ statusBarStateController.addCallback(
+ object : StatusBarStateController.StateListener {
+ override fun onStatePreChange(oldState: Int, newState: Int) {
+ // We're updating the location before the state change happens, since we want
+ // the
+ // location of the previous state to still be up to date when the animation
+ // starts
+ statusbarState = newState
+ updateDesiredLocation()
+ }
- override fun onDozingChanged(isDozing: Boolean) {
- if (!isDozing) {
- dozeAnimationRunning = false
- // Enters lock screen from screen off
- if (isLockScreenVisibleToUser()) {
+ override fun onStateChanged(newState: Int) {
+ updateTargetState()
+ // Enters shade from lock screen
+ if (
+ newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()
+ ) {
mediaCarouselController.logSmartspaceImpression(qsExpanded)
}
- } else {
- updateDesiredLocation()
- qsExpanded = false
- closeGuts()
+ mediaCarouselController.mediaCarouselScrollHandler.visibleToUser =
+ isVisibleToUser()
}
- mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
- }
- override fun onExpandedChanged(isExpanded: Boolean) {
- // Enters shade from home screen
- if (isHomeScreenShadeVisibleToUser()) {
- mediaCarouselController.logSmartspaceImpression(qsExpanded)
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ dozeAnimationRunning = linear != 0.0f && linear != 1.0f
}
- mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
- }
- })
- dreamOverlayStateController.addCallback(object : DreamOverlayStateController.Callback {
- override fun onComplicationsChanged() {
- dreamMediaComplicationActive = dreamOverlayStateController.complications.any {
- it is MediaDreamComplication
+ override fun onDozingChanged(isDozing: Boolean) {
+ if (!isDozing) {
+ dozeAnimationRunning = false
+ // Enters lock screen from screen off
+ if (isLockScreenVisibleToUser()) {
+ mediaCarouselController.logSmartspaceImpression(qsExpanded)
+ }
+ } else {
+ updateDesiredLocation()
+ qsExpanded = false
+ closeGuts()
+ }
+ mediaCarouselController.mediaCarouselScrollHandler.visibleToUser =
+ isVisibleToUser()
}
- }
- override fun onStateChanged() {
- dreamOverlayStateController.isOverlayActive.also { dreamOverlayActive = it }
+ override fun onExpandedChanged(isExpanded: Boolean) {
+ // Enters shade from home screen
+ if (isHomeScreenShadeVisibleToUser()) {
+ mediaCarouselController.logSmartspaceImpression(qsExpanded)
+ }
+ mediaCarouselController.mediaCarouselScrollHandler.visibleToUser =
+ isVisibleToUser()
+ }
}
- })
+ )
+
+ dreamOverlayStateController.addCallback(
+ object : DreamOverlayStateController.Callback {
+ override fun onComplicationsChanged() {
+ dreamMediaComplicationActive =
+ dreamOverlayStateController.complications.any {
+ it is MediaDreamComplication
+ }
+ }
- wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer {
- override fun onFinishedGoingToSleep() {
- goingToSleep = false
+ override fun onStateChanged() {
+ dreamOverlayStateController.isOverlayActive.also { dreamOverlayActive = it }
+ }
}
+ )
- override fun onStartedGoingToSleep() {
- goingToSleep = true
- fullyAwake = false
- }
+ wakefulnessLifecycle.addObserver(
+ object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ goingToSleep = false
+ }
- override fun onFinishedWakingUp() {
- goingToSleep = false
- fullyAwake = true
- }
+ override fun onStartedGoingToSleep() {
+ goingToSleep = true
+ fullyAwake = false
+ }
+
+ override fun onFinishedWakingUp() {
+ goingToSleep = false
+ fullyAwake = true
+ }
- override fun onStartedWakingUp() {
- goingToSleep = false
+ override fun onStartedWakingUp() {
+ goingToSleep = false
+ }
}
- })
+ )
mediaCarouselController.updateUserVisibility = {
mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
}
mediaCarouselController.updateHostVisibility = {
- mediaHosts.forEach {
- it?.updateViewVisibility()
- }
+ mediaHosts.forEach { it?.updateViewVisibility() }
}
- panelEventsEvents.registerListener(object : NotifPanelEvents.Listener {
- override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
- skipQqsOnExpansion = isExpandImmediateEnabled
- updateDesiredLocation()
+ panelEventsEvents.registerListener(
+ object : NotifPanelEvents.Listener {
+ override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
+ skipQqsOnExpansion = isExpandImmediateEnabled
+ updateDesiredLocation()
+ }
}
- })
+ )
- val settingsObserver: ContentObserver = object : ContentObserver(handler) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- if (uri == lockScreenMediaPlayerUri) {
- allowMediaPlayerOnLockScreen =
+ val settingsObserver: ContentObserver =
+ object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ if (uri == lockScreenMediaPlayerUri) {
+ allowMediaPlayerOnLockScreen =
secureSettings.getBoolForUser(
- Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
- true,
- UserHandle.USER_CURRENT
+ Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+ true,
+ UserHandle.USER_CURRENT
)
+ }
}
}
- }
secureSettings.registerContentObserverForUser(
- Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
- settingsObserver,
- UserHandle.USER_ALL)
+ Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+ settingsObserver,
+ UserHandle.USER_ALL
+ )
}
private fun updateConfiguration() {
- distanceForFullShadeTransition = context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_media_transition_distance)
+ distanceForFullShadeTransition =
+ context.resources.getDimensionPixelSize(
+ R.dimen.lockscreen_shade_media_transition_distance
+ )
inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources)
}
/**
- * Register a media host and create a view can be attached to a view hierarchy
- * and where the players will be placed in when the host is the currently desired state.
+ * Register a media host and create a view can be attached to a view hierarchy and where the
+ * players will be placed in when the host is the currently desired state.
*
* @return the hostView associated with this location
*/
@@ -613,27 +601,26 @@ class MediaHierarchyManager @Inject constructor(
return viewHost
}
- /**
- * Close the guts in all players in [MediaCarouselController].
- */
+ /** Close the guts in all players in [MediaCarouselController]. */
fun closeGuts() {
mediaCarouselController.closeGuts()
}
private fun createUniqueObjectHost(): UniqueObjectHostView {
val viewHost = UniqueObjectHostView(context)
- viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(p0: View?) {
- if (rootOverlay == null) {
- rootView = viewHost.viewRootImpl.view
- rootOverlay = (rootView!!.overlay as ViewGroupOverlay)
+ viewHost.addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(p0: View?) {
+ if (rootOverlay == null) {
+ rootView = viewHost.viewRootImpl.view
+ rootOverlay = (rootView!!.overlay as ViewGroupOverlay)
+ }
+ viewHost.removeOnAttachStateChangeListener(this)
}
- viewHost.removeOnAttachStateChangeListener(this)
- }
- override fun onViewDetachedFromWindow(p0: View?) {
+ override fun onViewDetachedFromWindow(p0: View?) {}
}
- })
+ )
return viewHost
}
@@ -643,127 +630,141 @@ class MediaHierarchyManager @Inject constructor(
*
* @param forceNoAnimation optional parameter telling the system not to animate
* @param forceStateUpdate optional parameter telling the system to update transition state
+ * ```
* even if location did not change
+ * ```
*/
private fun updateDesiredLocation(
forceNoAnimation: Boolean = false,
forceStateUpdate: Boolean = false
- ) = traceSection("MediaHierarchyManager#updateDesiredLocation") {
- val desiredLocation = calculateLocation()
- if (desiredLocation != this.desiredLocation || forceStateUpdate) {
- if (this.desiredLocation >= 0 && desiredLocation != this.desiredLocation) {
- // Only update previous location when it actually changes
- previousLocation = this.desiredLocation
- } else if (forceStateUpdate) {
- val onLockscreen = (!bypassController.bypassEnabled &&
- (statusbarState == StatusBarState.KEYGUARD))
- if (desiredLocation == LOCATION_QS && previousLocation == LOCATION_LOCKSCREEN &&
- !onLockscreen) {
- // If media active state changed and the device is now unlocked, update the
- // previous location so we animate between the correct hosts
- previousLocation = LOCATION_QQS
+ ) =
+ traceSection("MediaHierarchyManager#updateDesiredLocation") {
+ val desiredLocation = calculateLocation()
+ if (desiredLocation != this.desiredLocation || forceStateUpdate) {
+ if (this.desiredLocation >= 0 && desiredLocation != this.desiredLocation) {
+ // Only update previous location when it actually changes
+ previousLocation = this.desiredLocation
+ } else if (forceStateUpdate) {
+ val onLockscreen =
+ (!bypassController.bypassEnabled &&
+ (statusbarState == StatusBarState.KEYGUARD))
+ if (
+ desiredLocation == LOCATION_QS &&
+ previousLocation == LOCATION_LOCKSCREEN &&
+ !onLockscreen
+ ) {
+ // If media active state changed and the device is now unlocked, update the
+ // previous location so we animate between the correct hosts
+ previousLocation = LOCATION_QQS
+ }
}
+ val isNewView = this.desiredLocation == -1
+ this.desiredLocation = desiredLocation
+ // Let's perform a transition
+ val animate =
+ !forceNoAnimation && shouldAnimateTransition(desiredLocation, previousLocation)
+ val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
+ val host = getHost(desiredLocation)
+ val willFade = calculateTransformationType() == TRANSFORMATION_TYPE_FADE
+ if (!willFade || isCurrentlyInGuidedTransformation() || !animate) {
+ // if we're fading, we want the desired location / measurement only to change
+ // once fully faded. This is happening in the host attachment
+ mediaCarouselController.onDesiredLocationChanged(
+ desiredLocation,
+ host,
+ animate,
+ animDuration,
+ delay
+ )
+ }
+ performTransitionToNewLocation(isNewView, animate)
}
- val isNewView = this.desiredLocation == -1
- this.desiredLocation = desiredLocation
- // Let's perform a transition
- val animate = !forceNoAnimation &&
- shouldAnimateTransition(desiredLocation, previousLocation)
- val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
- val host = getHost(desiredLocation)
- val willFade = calculateTransformationType() == TRANSFORMATION_TYPE_FADE
- if (!willFade || isCurrentlyInGuidedTransformation() || !animate) {
- // if we're fading, we want the desired location / measurement only to change
- // once fully faded. This is happening in the host attachment
- mediaCarouselController.onDesiredLocationChanged(desiredLocation, host,
- animate, animDuration, delay)
- }
- performTransitionToNewLocation(isNewView, animate)
}
- }
- private fun performTransitionToNewLocation(
- isNewView: Boolean,
- animate: Boolean
- ) = traceSection("MediaHierarchyManager#performTransitionToNewLocation") {
- if (previousLocation < 0 || isNewView) {
- cancelAnimationAndApplyDesiredState()
- return
- }
- val currentHost = getHost(desiredLocation)
- val previousHost = getHost(previousLocation)
- if (currentHost == null || previousHost == null) {
- cancelAnimationAndApplyDesiredState()
- return
- }
- updateTargetState()
- if (isCurrentlyInGuidedTransformation()) {
- applyTargetStateIfNotAnimating()
- } else if (animate) {
- val wasCrossFading = isCrossFadeAnimatorRunning
- val previewsCrossFadeProgress = animationCrossFadeProgress
- animator.cancel()
- if (currentAttachmentLocation != previousLocation ||
- !previousHost.hostView.isAttachedToWindow) {
- // Let's animate to the new position, starting from the current position
- // We also go in here in case the view was detached, since the bounds wouldn't
- // be correct anymore
- animationStartBounds.set(currentBounds)
- animationStartClipping.set(currentClipping)
- } else {
- // otherwise, let's take the freshest state, since the current one could
- // be outdated
- animationStartBounds.set(previousHost.currentBounds)
- animationStartClipping.set(previousHost.currentClipping)
+ private fun performTransitionToNewLocation(isNewView: Boolean, animate: Boolean) =
+ traceSection("MediaHierarchyManager#performTransitionToNewLocation") {
+ if (previousLocation < 0 || isNewView) {
+ cancelAnimationAndApplyDesiredState()
+ return
}
- val transformationType = calculateTransformationType()
- var needsCrossFade = transformationType == TRANSFORMATION_TYPE_FADE
- var crossFadeStartProgress = 0.0f
- // The alpha is only relevant when not cross fading
- var newCrossFadeStartLocation = previousLocation
- if (wasCrossFading) {
- if (currentAttachmentLocation == crossFadeAnimationEndLocation) {
- if (needsCrossFade) {
- // We were previously crossFading and we've already reached
- // the end view, Let's start crossfading from the same position there
- crossFadeStartProgress = 1.0f - previewsCrossFadeProgress
- }
- // Otherwise let's fade in from the current alpha, but not cross fade
+ val currentHost = getHost(desiredLocation)
+ val previousHost = getHost(previousLocation)
+ if (currentHost == null || previousHost == null) {
+ cancelAnimationAndApplyDesiredState()
+ return
+ }
+ updateTargetState()
+ if (isCurrentlyInGuidedTransformation()) {
+ applyTargetStateIfNotAnimating()
+ } else if (animate) {
+ val wasCrossFading = isCrossFadeAnimatorRunning
+ val previewsCrossFadeProgress = animationCrossFadeProgress
+ animator.cancel()
+ if (
+ currentAttachmentLocation != previousLocation ||
+ !previousHost.hostView.isAttachedToWindow
+ ) {
+ // Let's animate to the new position, starting from the current position
+ // We also go in here in case the view was detached, since the bounds wouldn't
+ // be correct anymore
+ animationStartBounds.set(currentBounds)
+ animationStartClipping.set(currentClipping)
} else {
- // We haven't reached the previous location yet, let's still cross fade from
- // where we were.
- newCrossFadeStartLocation = crossFadeAnimationStartLocation
- if (newCrossFadeStartLocation == desiredLocation) {
- // we're crossFading back to where we were, let's start at the end position
- crossFadeStartProgress = 1.0f - previewsCrossFadeProgress
+ // otherwise, let's take the freshest state, since the current one could
+ // be outdated
+ animationStartBounds.set(previousHost.currentBounds)
+ animationStartClipping.set(previousHost.currentClipping)
+ }
+ val transformationType = calculateTransformationType()
+ var needsCrossFade = transformationType == TRANSFORMATION_TYPE_FADE
+ var crossFadeStartProgress = 0.0f
+ // The alpha is only relevant when not cross fading
+ var newCrossFadeStartLocation = previousLocation
+ if (wasCrossFading) {
+ if (currentAttachmentLocation == crossFadeAnimationEndLocation) {
+ if (needsCrossFade) {
+ // We were previously crossFading and we've already reached
+ // the end view, Let's start crossfading from the same position there
+ crossFadeStartProgress = 1.0f - previewsCrossFadeProgress
+ }
+ // Otherwise let's fade in from the current alpha, but not cross fade
} else {
- // Let's start from where we are right now
- crossFadeStartProgress = previewsCrossFadeProgress
- // We need to force cross fading as we haven't reached the end location yet
- needsCrossFade = true
+ // We haven't reached the previous location yet, let's still cross fade from
+ // where we were.
+ newCrossFadeStartLocation = crossFadeAnimationStartLocation
+ if (newCrossFadeStartLocation == desiredLocation) {
+ // we're crossFading back to where we were, let's start at the end
+ // position
+ crossFadeStartProgress = 1.0f - previewsCrossFadeProgress
+ } else {
+ // Let's start from where we are right now
+ crossFadeStartProgress = previewsCrossFadeProgress
+ // We need to force cross fading as we haven't reached the end location
+ // yet
+ needsCrossFade = true
+ }
}
+ } else if (needsCrossFade) {
+ // let's not flicker and start with the same alpha
+ crossFadeStartProgress = (1.0f - carouselAlpha) / 2.0f
}
- } else if (needsCrossFade) {
- // let's not flicker and start with the same alpha
- crossFadeStartProgress = (1.0f - carouselAlpha) / 2.0f
- }
- isCrossFadeAnimatorRunning = needsCrossFade
- crossFadeAnimationStartLocation = newCrossFadeStartLocation
- crossFadeAnimationEndLocation = desiredLocation
- animationStartAlpha = carouselAlpha
- animationStartCrossFadeProgress = crossFadeStartProgress
- adjustAnimatorForTransition(desiredLocation, previousLocation)
- if (!animationPending) {
- rootView?.let {
- // Let's delay the animation start until we finished laying out
- animationPending = true
- it.postOnAnimation(startAnimation)
+ isCrossFadeAnimatorRunning = needsCrossFade
+ crossFadeAnimationStartLocation = newCrossFadeStartLocation
+ crossFadeAnimationEndLocation = desiredLocation
+ animationStartAlpha = carouselAlpha
+ animationStartCrossFadeProgress = crossFadeStartProgress
+ adjustAnimatorForTransition(desiredLocation, previousLocation)
+ if (!animationPending) {
+ rootView?.let {
+ // Let's delay the animation start until we finished laying out
+ animationPending = true
+ it.postOnAnimation(startAnimation)
+ }
}
+ } else {
+ cancelAnimationAndApplyDesiredState()
}
- } else {
- cancelAnimationAndApplyDesiredState()
}
- }
private fun shouldAnimateTransition(
@MediaLocation currentLocation: Int,
@@ -777,23 +778,29 @@ class MediaHierarchyManager @Inject constructor(
}
// This is an invalid transition, and can happen when using the camera gesture from the
// lock screen. Disallow.
- if (previousLocation == LOCATION_LOCKSCREEN &&
- desiredLocation == LOCATION_QQS &&
- statusbarState == StatusBarState.SHADE) {
+ if (
+ previousLocation == LOCATION_LOCKSCREEN &&
+ desiredLocation == LOCATION_QQS &&
+ statusbarState == StatusBarState.SHADE
+ ) {
return false
}
- if (currentLocation == LOCATION_QQS &&
+ if (
+ currentLocation == LOCATION_QQS &&
previousLocation == LOCATION_LOCKSCREEN &&
(statusBarStateController.leaveOpenOnKeyguardHide() ||
- statusbarState == StatusBarState.SHADE_LOCKED)) {
+ statusbarState == StatusBarState.SHADE_LOCKED)
+ ) {
// Usually listening to the isShown is enough to determine this, but there is some
// non-trivial reattaching logic happening that will make the view not-shown earlier
return true
}
- if (statusbarState == StatusBarState.KEYGUARD && (currentLocation == LOCATION_LOCKSCREEN ||
- previousLocation == LOCATION_LOCKSCREEN)) {
+ if (
+ statusbarState == StatusBarState.KEYGUARD &&
+ (currentLocation == LOCATION_LOCKSCREEN || previousLocation == LOCATION_LOCKSCREEN)
+ ) {
// We're always fading from lockscreen to keyguard in situations where the player
// is already fully hidden
return false
@@ -814,8 +821,10 @@ class MediaHierarchyManager @Inject constructor(
var delay = 0L
if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) {
// Going to the full shade, let's adjust the animation duration
- if (statusbarState == StatusBarState.SHADE &&
- keyguardStateController.isKeyguardFadingAway) {
+ if (
+ statusbarState == StatusBarState.SHADE &&
+ keyguardStateController.isKeyguardFadingAway
+ ) {
delay = keyguardStateController.keyguardFadingAwayDelay
}
animDuration = (StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE / 2f).toLong()
@@ -834,14 +843,16 @@ class MediaHierarchyManager @Inject constructor(
}
}
- /**
- * Updates the bounds that the view wants to be in at the end of the animation.
- */
+ /** Updates the bounds that the view wants to be in at the end of the animation. */
private fun updateTargetState() {
var starthost = getHost(previousLocation)
var endHost = getHost(desiredLocation)
- if (isCurrentlyInGuidedTransformation() && !isCurrentlyFading() && starthost != null &&
- endHost != null) {
+ if (
+ isCurrentlyInGuidedTransformation() &&
+ !isCurrentlyFading() &&
+ starthost != null &&
+ endHost != null
+ ) {
val progress = getTransformationProgress()
// If either of the hosts are invisible, let's keep them at the other host location to
// have a nicer disappear animation. Otherwise the currentBounds of the state might
@@ -868,14 +879,15 @@ class MediaHierarchyManager @Inject constructor(
progress: Float,
result: Rect? = null
): Rect {
- val left = MathUtils.lerp(startBounds.left.toFloat(),
- endBounds.left.toFloat(), progress).toInt()
- val top = MathUtils.lerp(startBounds.top.toFloat(),
- endBounds.top.toFloat(), progress).toInt()
- val right = MathUtils.lerp(startBounds.right.toFloat(),
- endBounds.right.toFloat(), progress).toInt()
- val bottom = MathUtils.lerp(startBounds.bottom.toFloat(),
- endBounds.bottom.toFloat(), progress).toInt()
+ val left =
+ MathUtils.lerp(startBounds.left.toFloat(), endBounds.left.toFloat(), progress).toInt()
+ val top =
+ MathUtils.lerp(startBounds.top.toFloat(), endBounds.top.toFloat(), progress).toInt()
+ val right =
+ MathUtils.lerp(startBounds.right.toFloat(), endBounds.right.toFloat(), progress).toInt()
+ val bottom =
+ MathUtils.lerp(startBounds.bottom.toFloat(), endBounds.bottom.toFloat(), progress)
+ .toInt()
val resultBounds = result ?: Rect()
resultBounds.set(left, top, right, bottom)
return resultBounds
@@ -884,17 +896,15 @@ class MediaHierarchyManager @Inject constructor(
/** @return true if this transformation is guided by an external progress like a finger */
fun isCurrentlyInGuidedTransformation(): Boolean {
return hasValidStartAndEndLocations() &&
- getTransformationProgress() >= 0 &&
- areGuidedTransitionHostsVisible()
+ getTransformationProgress() >= 0 &&
+ areGuidedTransitionHostsVisible()
}
private fun hasValidStartAndEndLocations(): Boolean {
return previousLocation != -1 && desiredLocation != -1
}
- /**
- * Calculate the transformation type for the current animation
- */
+ /** Calculate the transformation type for the current animation */
@VisibleForTesting
@TransformationType
fun calculateTransformationType(): Int {
@@ -904,8 +914,10 @@ class MediaHierarchyManager @Inject constructor(
}
return TRANSFORMATION_TYPE_FADE
}
- if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS ||
- previousLocation == LOCATION_QS && desiredLocation == LOCATION_LOCKSCREEN) {
+ if (
+ previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS ||
+ previousLocation == LOCATION_QS && desiredLocation == LOCATION_LOCKSCREEN
+ ) {
// animating between ls and qs should fade, as QS is clipped.
return TRANSFORMATION_TYPE_FADE
}
@@ -918,7 +930,7 @@ class MediaHierarchyManager @Inject constructor(
private fun areGuidedTransitionHostsVisible(): Boolean {
return getHost(previousLocation)?.visible == true &&
- getHost(desiredLocation)?.visible == true
+ getHost(desiredLocation)?.visible == true
}
/**
@@ -966,103 +978,115 @@ class MediaHierarchyManager @Inject constructor(
}
}
- /**
- * Apply the current state to the view, updating it's bounds and desired state
- */
+ /** Apply the current state to the view, updating it's bounds and desired state */
private fun applyState(
bounds: Rect,
alpha: Float,
immediately: Boolean = false,
clipBounds: Rect = EMPTY_RECT
- ) = traceSection("MediaHierarchyManager#applyState") {
- currentBounds.set(bounds)
- currentClipping = clipBounds
- carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f
- val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading()
- val startLocation = if (onlyUseEndState) -1 else previousLocation
- val progress = if (onlyUseEndState) 1.0f else getTransformationProgress()
- val endLocation = resolveLocationForFading()
- mediaCarouselController.setCurrentState(startLocation, endLocation, progress, immediately)
- updateHostAttachment()
- if (currentAttachmentLocation == IN_OVERLAY) {
- // Setting the clipping on the hierarchy of `mediaFrame` does not work
- if (!currentClipping.isEmpty) {
- currentBounds.intersect(currentClipping)
- }
- mediaFrame.setLeftTopRightBottom(
+ ) =
+ traceSection("MediaHierarchyManager#applyState") {
+ currentBounds.set(bounds)
+ currentClipping = clipBounds
+ carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f
+ val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading()
+ val startLocation = if (onlyUseEndState) -1 else previousLocation
+ val progress = if (onlyUseEndState) 1.0f else getTransformationProgress()
+ val endLocation = resolveLocationForFading()
+ mediaCarouselController.setCurrentState(
+ startLocation,
+ endLocation,
+ progress,
+ immediately
+ )
+ updateHostAttachment()
+ if (currentAttachmentLocation == IN_OVERLAY) {
+ // Setting the clipping on the hierarchy of `mediaFrame` does not work
+ if (!currentClipping.isEmpty) {
+ currentBounds.intersect(currentClipping)
+ }
+ mediaFrame.setLeftTopRightBottom(
currentBounds.left,
currentBounds.top,
currentBounds.right,
- currentBounds.bottom)
+ currentBounds.bottom
+ )
+ }
}
- }
- private fun updateHostAttachment() = traceSection(
- "MediaHierarchyManager#updateHostAttachment"
- ) {
- var newLocation = resolveLocationForFading()
- var canUseOverlay = !isCurrentlyFading()
- if (isCrossFadeAnimatorRunning) {
- if (getHost(newLocation)?.visible == true &&
- getHost(newLocation)?.hostView?.isShown == false &&
- newLocation != desiredLocation) {
- // We're crossfading but the view is already hidden. Let's move to the overlay
- // instead. This happens when animating to the full shade using a button click.
- canUseOverlay = true
+ private fun updateHostAttachment() =
+ traceSection("MediaHierarchyManager#updateHostAttachment") {
+ var newLocation = resolveLocationForFading()
+ var canUseOverlay = !isCurrentlyFading()
+ if (isCrossFadeAnimatorRunning) {
+ if (
+ getHost(newLocation)?.visible == true &&
+ getHost(newLocation)?.hostView?.isShown == false &&
+ newLocation != desiredLocation
+ ) {
+ // We're crossfading but the view is already hidden. Let's move to the overlay
+ // instead. This happens when animating to the full shade using a button click.
+ canUseOverlay = true
+ }
}
- }
- val inOverlay = isTransitionRunning() && rootOverlay != null && canUseOverlay
- newLocation = if (inOverlay) IN_OVERLAY else newLocation
- if (currentAttachmentLocation != newLocation) {
- currentAttachmentLocation = newLocation
+ val inOverlay = isTransitionRunning() && rootOverlay != null && canUseOverlay
+ newLocation = if (inOverlay) IN_OVERLAY else newLocation
+ if (currentAttachmentLocation != newLocation) {
+ currentAttachmentLocation = newLocation
- // Remove the carousel from the old host
- (mediaFrame.parent as ViewGroup?)?.removeView(mediaFrame)
+ // Remove the carousel from the old host
+ (mediaFrame.parent as ViewGroup?)?.removeView(mediaFrame)
- // Add it to the new one
- if (inOverlay) {
- rootOverlay!!.add(mediaFrame)
- } else {
- val targetHost = getHost(newLocation)!!.hostView
- // When adding back to the host, let's make sure to reset the bounds.
- // Usually adding the view will trigger a layout that does this automatically,
- // but we sometimes suppress this.
- targetHost.addView(mediaFrame)
- val left = targetHost.paddingLeft
- val top = targetHost.paddingTop
- mediaFrame.setLeftTopRightBottom(
+ // Add it to the new one
+ if (inOverlay) {
+ rootOverlay!!.add(mediaFrame)
+ } else {
+ val targetHost = getHost(newLocation)!!.hostView
+ // When adding back to the host, let's make sure to reset the bounds.
+ // Usually adding the view will trigger a layout that does this automatically,
+ // but we sometimes suppress this.
+ targetHost.addView(mediaFrame)
+ val left = targetHost.paddingLeft
+ val top = targetHost.paddingTop
+ mediaFrame.setLeftTopRightBottom(
left,
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}")
+ 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
- // location change, once the view is reattached to the new place and not immediately
- // when the desired location changes. This callback will update the measurement
- // of the carousel, only once we've faded out at the old location and then reattach
- // to fade it in at the new location.
- mediaCarouselController.onDesiredLocationChanged(
- newLocation,
- getHost(newLocation),
- animate = false
- )
+ if (isCrossFadeAnimatorRunning) {
+ // When cross-fading with an animation, we only notify the media carousel of the
+ // location change, once the view is reattached to the new place and not
+ // immediately
+ // when the desired location changes. This callback will update the measurement
+ // of the carousel, only once we've faded out at the old location and then
+ // reattach
+ // to fade it in at the new location.
+ mediaCarouselController.onDesiredLocationChanged(
+ newLocation,
+ getHost(newLocation),
+ animate = false
+ )
+ }
}
}
- }
/**
- * Calculate the location when cross fading between locations. While fading out,
- * the content should remain in the previous location, while after the switch it should
- * be at the desired location.
+ * Calculate the location when cross fading between locations. While fading out, the content
+ * should remain in the previous location, while after the switch it should be at the desired
+ * location.
*/
private fun resolveLocationForFading(): Int {
if (isCrossFadeAnimatorRunning) {
@@ -1079,7 +1103,8 @@ class MediaHierarchyManager @Inject constructor(
private fun isTransitionRunning(): Boolean {
return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f ||
- animator.isRunning || animationPending
+ animator.isRunning ||
+ animationPending
}
@MediaLocation
@@ -1088,31 +1113,39 @@ class MediaHierarchyManager @Inject constructor(
// Keep the current location until we're allowed to again
return desiredLocation
}
- val onLockscreen = (!bypassController.bypassEnabled &&
- (statusbarState == StatusBarState.KEYGUARD))
- val location = when {
- dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
- (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
- qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
- !hasActiveMedia -> LOCATION_QS
- onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
- onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
- onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
- else -> LOCATION_QQS
- }
+ val onLockscreen =
+ (!bypassController.bypassEnabled && (statusbarState == StatusBarState.KEYGUARD))
+ val location =
+ when {
+ dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
+ (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
+ qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
+ !hasActiveMedia -> LOCATION_QS
+ onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
+ onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
+ onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
+ else -> LOCATION_QQS
+ }
// When we're on lock screen and the player is not active, we should keep it in QS.
// Otherwise it will try to animate a transition that doesn't make sense.
- if (location == LOCATION_LOCKSCREEN && getHost(location)?.visible != true &&
- !statusBarStateController.isDozing) {
+ if (
+ location == LOCATION_LOCKSCREEN &&
+ getHost(location)?.visible != true &&
+ !statusBarStateController.isDozing
+ ) {
return LOCATION_QS
}
- if (location == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS &&
- collapsingShadeFromQS) {
+ if (
+ location == LOCATION_LOCKSCREEN &&
+ desiredLocation == LOCATION_QS &&
+ collapsingShadeFromQS
+ ) {
// When collapsing on the lockscreen, we want to remain in QS
return LOCATION_QS
}
- if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN &&
- !fullyAwake) {
+ if (
+ location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN && !fullyAwake
+ ) {
// When unlocking from dozing / while waking up, the media shouldn't be transitioning
// in an animated way. Let's keep it in the lockscreen until we're fully awake and
// reattach it without an animation
@@ -1129,9 +1162,7 @@ class MediaHierarchyManager @Inject constructor(
return inSplitShade && isTransitioningToFullShade
}
- /**
- * Are we currently transforming to the full shade and already in QQS
- */
+ /** Are we currently transforming to the full shade and already in QQS */
private fun isTransformingToFullShadeAndInQQS(): Boolean {
if (!isTransitioningToFullShade) {
return false
@@ -1143,9 +1174,7 @@ class MediaHierarchyManager @Inject constructor(
return fullShadeTransitionProgress > 0.5f
}
- /**
- * Is the current transformationType fading
- */
+ /** Is the current transformationType fading */
private fun isCurrentlyFading(): Boolean {
if (isSplitShadeExpanding()) {
// Split shade always uses transition instead of fade.
@@ -1157,60 +1186,49 @@ class MediaHierarchyManager @Inject constructor(
return isCrossFadeAnimatorRunning
}
- /**
- * Returns true when the media card could be visible to the user if existed.
- */
+ /** Returns true when the media card could be visible to the user if existed. */
private fun isVisibleToUser(): Boolean {
- return isLockScreenVisibleToUser() || isLockScreenShadeVisibleToUser() ||
- isHomeScreenShadeVisibleToUser()
+ return isLockScreenVisibleToUser() ||
+ isLockScreenShadeVisibleToUser() ||
+ isHomeScreenShadeVisibleToUser()
}
private fun isLockScreenVisibleToUser(): Boolean {
return !statusBarStateController.isDozing &&
- !keyguardViewController.isBouncerShowing &&
- statusBarStateController.state == StatusBarState.KEYGUARD &&
- allowMediaPlayerOnLockScreen &&
- statusBarStateController.isExpanded &&
- !qsExpanded
+ !keyguardViewController.isBouncerShowing &&
+ statusBarStateController.state == StatusBarState.KEYGUARD &&
+ allowMediaPlayerOnLockScreen &&
+ statusBarStateController.isExpanded &&
+ !qsExpanded
}
private fun isLockScreenShadeVisibleToUser(): Boolean {
return !statusBarStateController.isDozing &&
- !keyguardViewController.isBouncerShowing &&
- (statusBarStateController.state == StatusBarState.SHADE_LOCKED ||
- (statusBarStateController.state == StatusBarState.KEYGUARD && qsExpanded))
+ !keyguardViewController.isBouncerShowing &&
+ (statusBarStateController.state == StatusBarState.SHADE_LOCKED ||
+ (statusBarStateController.state == StatusBarState.KEYGUARD && qsExpanded))
}
private fun isHomeScreenShadeVisibleToUser(): Boolean {
return !statusBarStateController.isDozing &&
- statusBarStateController.state == StatusBarState.SHADE &&
- statusBarStateController.isExpanded
+ statusBarStateController.state == StatusBarState.SHADE &&
+ statusBarStateController.isExpanded
}
companion object {
- /**
- * Attached in expanded quick settings
- */
+ /** Attached in expanded quick settings */
const val LOCATION_QS = 0
- /**
- * Attached in the collapsed QS
- */
+ /** Attached in the collapsed QS */
const val LOCATION_QQS = 1
- /**
- * Attached on the lock screen
- */
+ /** Attached on the lock screen */
const val LOCATION_LOCKSCREEN = 2
- /**
- * Attached on the dream overlay
- */
+ /** Attached on the dream overlay */
const val LOCATION_DREAM_OVERLAY = 3
- /**
- * Attached at the root of the hierarchy in an overlay
- */
+ /** Attached at the root of the hierarchy in an overlay */
const val IN_OVERLAY = -1000
/**
@@ -1226,18 +1244,29 @@ class MediaHierarchyManager @Inject constructor(
const val TRANSFORMATION_TYPE_FADE = 1
}
}
+
private val EMPTY_RECT = Rect()
-@IntDef(prefix = ["TRANSFORMATION_TYPE_"], value = [
- MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION,
- MediaHierarchyManager.TRANSFORMATION_TYPE_FADE])
+@IntDef(
+ prefix = ["TRANSFORMATION_TYPE_"],
+ value =
+ [
+ MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION,
+ MediaHierarchyManager.TRANSFORMATION_TYPE_FADE
+ ]
+)
@Retention(AnnotationRetention.SOURCE)
private annotation class TransformationType
-@IntDef(prefix = ["LOCATION_"], value = [
- MediaHierarchyManager.LOCATION_QS,
- MediaHierarchyManager.LOCATION_QQS,
- MediaHierarchyManager.LOCATION_LOCKSCREEN,
- MediaHierarchyManager.LOCATION_DREAM_OVERLAY])
+@IntDef(
+ prefix = ["LOCATION_"],
+ value =
+ [
+ MediaHierarchyManager.LOCATION_QS,
+ MediaHierarchyManager.LOCATION_QQS,
+ MediaHierarchyManager.LOCATION_LOCKSCREEN,
+ MediaHierarchyManager.LOCATION_DREAM_OVERLAY
+ ]
+)
@Retention(AnnotationRetention.SOURCE)
annotation class MediaLocation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
index 864592238b73..455b7de3dc0c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
@@ -1,9 +1,28 @@
-package com.android.systemui.media
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.controls.ui
import android.graphics.Rect
import android.util.ArraySet
import android.view.View
import android.view.View.OnAttachStateChangeListener
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.util.animation.DisappearParameters
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.MeasurementOutput
@@ -11,7 +30,8 @@ import com.android.systemui.util.animation.UniqueObjectHostView
import java.util.Objects
import javax.inject.Inject
-class MediaHost constructor(
+class MediaHost
+constructor(
private val state: MediaHostStateHolder,
private val mediaHierarchyManager: MediaHierarchyManager,
private val mediaDataManager: MediaDataManager,
@@ -26,14 +46,10 @@ class MediaHost constructor(
private var inited: Boolean = false
- /**
- * Are we listening to media data changes?
- */
+ /** Are we listening to media data changes? */
private var listeningToMediaData = false
- /**
- * Get the current bounds on the screen. This makes sure the state is fresh and up to date
- */
+ /** Get the current bounds on the screen. This makes sure the state is fresh and up to date */
val currentBounds: Rect = Rect()
get() {
hostView.getLocationOnScreen(tmpLocationOnScreen)
@@ -62,38 +78,39 @@ class MediaHost constructor(
*/
val currentClipping = Rect()
- private val listener = object : MediaDataManager.Listener {
- override fun onMediaDataLoaded(
- key: String,
- oldKey: String?,
- data: MediaData,
- immediately: Boolean,
- receivedSmartspaceCardLatency: Int,
- isSsReactivated: Boolean
- ) {
- if (immediately) {
- updateViewVisibility()
+ private val listener =
+ object : MediaDataManager.Listener {
+ override fun onMediaDataLoaded(
+ key: String,
+ oldKey: String?,
+ data: MediaData,
+ immediately: Boolean,
+ receivedSmartspaceCardLatency: Int,
+ isSsReactivated: Boolean
+ ) {
+ if (immediately) {
+ updateViewVisibility()
+ }
}
- }
- override fun onSmartspaceMediaDataLoaded(
- key: String,
- data: SmartspaceMediaData,
- shouldPrioritize: Boolean
- ) {
- updateViewVisibility()
- }
-
- override fun onMediaDataRemoved(key: String) {
- updateViewVisibility()
- }
+ override fun onSmartspaceMediaDataLoaded(
+ key: String,
+ data: SmartspaceMediaData,
+ shouldPrioritize: Boolean
+ ) {
+ updateViewVisibility()
+ }
- override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
- if (immediately) {
+ override fun onMediaDataRemoved(key: String) {
updateViewVisibility()
}
+
+ override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
+ if (immediately) {
+ updateViewVisibility()
+ }
+ }
}
- }
fun addVisibilityChangeListener(listener: (Boolean) -> Unit) {
visibleChangedListeners.add(listener)
@@ -104,12 +121,14 @@ class MediaHost constructor(
}
/**
- * Initialize this MediaObject and create a host view.
- * All state should already be set on this host before calling this method in order to avoid
- * unnecessary state changes which lead to remeasurings later on.
+ * Initialize this MediaObject and create a host view. All state should already be set on this
+ * host before calling this method in order to avoid unnecessary state changes which lead to
+ * remeasurings later on.
*
* @param location the location this host name has. Used to identify the host during
+ * ```
* transitions.
+ * ```
*/
fun init(@MediaLocation location: Int) {
if (inited) {
@@ -122,36 +141,42 @@ class MediaHost constructor(
// Listen by default, as the host might not be attached by our clients, until
// they get a visibility change. We still want to stay up to date in that case!
setListeningToMediaData(true)
- hostView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(v: View?) {
- setListeningToMediaData(true)
- updateViewVisibility()
- }
+ hostView.addOnAttachStateChangeListener(
+ object : OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View?) {
+ setListeningToMediaData(true)
+ updateViewVisibility()
+ }
- override fun onViewDetachedFromWindow(v: View?) {
- setListeningToMediaData(false)
+ override fun onViewDetachedFromWindow(v: View?) {
+ setListeningToMediaData(false)
+ }
}
- })
+ )
// Listen to measurement updates and update our state with it
- hostView.measurementManager = object : UniqueObjectHostView.MeasurementManager {
- override fun onMeasure(input: MeasurementInput): MeasurementOutput {
- // Modify the measurement to exactly match the dimensions
- if (View.MeasureSpec.getMode(input.widthMeasureSpec) == View.MeasureSpec.AT_MOST) {
- input.widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
- View.MeasureSpec.getSize(input.widthMeasureSpec),
- View.MeasureSpec.EXACTLY)
+ hostView.measurementManager =
+ object : UniqueObjectHostView.MeasurementManager {
+ override fun onMeasure(input: MeasurementInput): MeasurementOutput {
+ // Modify the measurement to exactly match the dimensions
+ if (
+ View.MeasureSpec.getMode(input.widthMeasureSpec) == View.MeasureSpec.AT_MOST
+ ) {
+ input.widthMeasureSpec =
+ View.MeasureSpec.makeMeasureSpec(
+ View.MeasureSpec.getSize(input.widthMeasureSpec),
+ View.MeasureSpec.EXACTLY
+ )
+ }
+ // This will trigger a state change that ensures that we now have a state
+ // available
+ state.measurementInput = input
+ return mediaHostStatesManager.updateCarouselDimensions(location, state)
}
- // This will trigger a state change that ensures that we now have a state available
- state.measurementInput = input
- return mediaHostStatesManager.updateCarouselDimensions(location, state)
}
- }
// Whenever the state changes, let our state manager know
- state.changedListener = {
- mediaHostStatesManager.updateHostState(location, state)
- }
+ state.changedListener = { mediaHostStatesManager.updateHostState(location, state) }
updateViewVisibility()
}
@@ -172,17 +197,16 @@ class MediaHost constructor(
* the visibility has changed
*/
fun updateViewVisibility() {
- state.visible = if (showsOnlyActiveMedia) {
- mediaDataManager.hasActiveMediaOrRecommendation()
- } else {
- mediaDataManager.hasAnyMediaOrRecommendation()
- }
+ state.visible =
+ if (showsOnlyActiveMedia) {
+ mediaDataManager.hasActiveMediaOrRecommendation()
+ } else {
+ mediaDataManager.hasAnyMediaOrRecommendation()
+ }
val newVisibility = if (visible) View.VISIBLE else View.GONE
if (newVisibility != hostView.visibility) {
hostView.visibility = newVisibility
- visibleChangedListeners.forEach {
- it.invoke(visible)
- }
+ visibleChangedListeners.forEach { it.invoke(visible) }
}
}
@@ -250,14 +274,10 @@ class MediaHost constructor(
private var lastDisappearHash = disappearParameters.hashCode()
- /**
- * A listener for all changes. This won't be copied over when invoking [copy]
- */
+ /** A listener for all changes. This won't be copied over when invoking [copy] */
var changedListener: (() -> Unit)? = null
- /**
- * Get a copy of this state. This won't copy any listeners it may have set
- */
+ /** Get a copy of this state. This won't copy any listeners it may have set */
override fun copy(): MediaHostState {
val mediaHostState = MediaHostStateHolder()
mediaHostState.expansion = expansion
@@ -312,15 +332,13 @@ class MediaHost constructor(
}
/**
- * A description of a media host state that describes the behavior whenever the media carousel
- * is hosted. The HostState notifies the media players of changes to their properties, who
- * in turn will create view states from it.
- * When adding a new property to this, make sure to update the listener and notify them
- * about the changes.
- * In case you need to have a different rendering based on the state, you can add a new
- * constraintState to the [MediaViewController]. Otherwise, similar host states will resolve
- * to the same viewstate, a behavior that is described in [CacheKey]. Make sure to only update
- * that key if the underlying view needs to have a different measurement.
+ * A description of a media host state that describes the behavior whenever the media carousel is
+ * hosted. The HostState notifies the media players of changes to their properties, who in turn will
+ * create view states from it. When adding a new property to this, make sure to update the listener
+ * and notify them about the changes. In case you need to have a different rendering based on the
+ * state, you can add a new constraintState to the [MediaViewController]. Otherwise, similar host
+ * states will resolve to the same viewstate, a behavior that is described in [CacheKey]. Make sure
+ * to only update that key if the underlying view needs to have a different measurement.
*/
interface MediaHostState {
@@ -330,46 +348,36 @@ interface MediaHostState {
}
/**
- * The last measurement input that this state was measured with. Infers width and height of
- * the players.
+ * The last measurement input that this state was measured with. Infers width and height of the
+ * players.
*/
var measurementInput: MeasurementInput?
/**
- * The expansion of the player, [COLLAPSED] for fully collapsed (up to 3 actions),
- * [EXPANDED] for fully expanded (up to 5 actions).
+ * The expansion of the player, [COLLAPSED] for fully collapsed (up to 3 actions), [EXPANDED]
+ * for fully expanded (up to 5 actions).
*/
var expansion: Float
- /**
- * Fraction of the height animation.
- */
+ /** Fraction of the height animation. */
var squishFraction: Float
- /**
- * Is this host only showing active media or is it showing all of them including resumption?
- */
+ /** Is this host only showing active media or is it showing all of them including resumption? */
var showsOnlyActiveMedia: Boolean
- /**
- * If the view should be VISIBLE or GONE.
- */
+ /** If the view should be VISIBLE or GONE. */
val visible: Boolean
- /**
- * Does this host need any falsing protection?
- */
+ /** Does this host need any falsing protection? */
var falsingProtectionNeeded: Boolean
/**
* The parameters how the view disappears from this location when going to a host that's not
- * visible. If modified, make sure to set this value again on the host to ensure the values
- * are propagated
+ * visible. If modified, make sure to set this value again on the host to ensure the values are
+ * propagated
*/
var disappearParameters: DisappearParameters
- /**
- * Get a copy of this view state, deepcopying all appropriate members
- */
+ /** Get a copy of this view state, deepcopying all appropriate members */
fun copy(): MediaHostState
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
index aea2934c46fe..ae3ce333d41d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.animation.MeasurementOutput
@@ -38,85 +38,76 @@ class MediaHostStatesManager @Inject constructor() {
*/
val carouselSizes: MutableMap<Int, MeasurementOutput> = mutableMapOf()
- /**
- * A map with all media states of all locations.
- */
+ /** A map with all media states of all locations. */
val mediaHostStates: MutableMap<Int, MediaHostState> = mutableMapOf()
/**
- * Notify that a media state for a given location has changed. Should only be called from
- * Media hosts themselves.
+ * Notify that a media state for a given location has changed. Should only be called from Media
+ * hosts themselves.
*/
- fun updateHostState(
- @MediaLocation location: Int,
- hostState: MediaHostState
- ) = traceSection("MediaHostStatesManager#updateHostState") {
- val currentState = mediaHostStates.get(location)
- if (!hostState.equals(currentState)) {
- val newState = hostState.copy()
- mediaHostStates.put(location, newState)
- updateCarouselDimensions(location, hostState)
- // First update all the controllers to ensure they get the chance to measure
- for (controller in controllers) {
- controller.stateCallback.onHostStateChanged(location, newState)
- }
+ fun updateHostState(@MediaLocation location: Int, hostState: MediaHostState) =
+ traceSection("MediaHostStatesManager#updateHostState") {
+ val currentState = mediaHostStates.get(location)
+ if (!hostState.equals(currentState)) {
+ val newState = hostState.copy()
+ mediaHostStates.put(location, newState)
+ updateCarouselDimensions(location, hostState)
+ // First update all the controllers to ensure they get the chance to measure
+ for (controller in controllers) {
+ controller.stateCallback.onHostStateChanged(location, newState)
+ }
- // Then update all other callbacks which may depend on the controllers above
- for (callback in callbacks) {
- callback.onHostStateChanged(location, newState)
+ // Then update all other callbacks which may depend on the controllers above
+ for (callback in callbacks) {
+ callback.onHostStateChanged(location, newState)
+ }
}
}
- }
/**
- * Get the dimensions of all players combined, which determines the overall height of the
- * media carousel and the media hosts.
+ * Get the dimensions of all players combined, which determines the overall height of the media
+ * carousel and the media hosts.
*/
fun updateCarouselDimensions(
@MediaLocation location: Int,
hostState: MediaHostState
- ): MeasurementOutput = traceSection("MediaHostStatesManager#updateCarouselDimensions") {
- val result = MeasurementOutput(0, 0)
- for (controller in controllers) {
- val measurement = controller.getMeasurementsForState(hostState)
- measurement?.let {
- if (it.measuredHeight > result.measuredHeight) {
- result.measuredHeight = it.measuredHeight
- }
- if (it.measuredWidth > result.measuredWidth) {
- result.measuredWidth = it.measuredWidth
+ ): MeasurementOutput =
+ traceSection("MediaHostStatesManager#updateCarouselDimensions") {
+ val result = MeasurementOutput(0, 0)
+ for (controller in controllers) {
+ val measurement = controller.getMeasurementsForState(hostState)
+ measurement?.let {
+ if (it.measuredHeight > result.measuredHeight) {
+ result.measuredHeight = it.measuredHeight
+ }
+ if (it.measuredWidth > result.measuredWidth) {
+ result.measuredWidth = it.measuredWidth
+ }
}
}
+ carouselSizes[location] = result
+ return result
}
- carouselSizes[location] = result
- return result
- }
- /**
- * Add a callback to be called when a MediaState has updated
- */
+ /** Add a callback to be called when a MediaState has updated */
fun addCallback(callback: Callback) {
callbacks.add(callback)
}
- /**
- * Remove a callback that listens to media states
- */
+ /** Remove a callback that listens to media states */
fun removeCallback(callback: Callback) {
callbacks.remove(callback)
}
/**
- * Register a controller that listens to media states and is used to determine the size of
- * the media carousel
+ * Register a controller that listens to media states and is used to determine the size of the
+ * media carousel
*/
fun addController(controller: MediaViewController) {
controllers.add(controller)
}
- /**
- * Notify the manager about the removal of a controller.
- */
+ /** Notify the manager about the removal of a controller. */
fun removeController(controller: MediaViewController) {
controllers.remove(controller)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt
index 00273bc34552..0e0746590776 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.media
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.controls.ui
import android.content.Context
import android.os.SystemClock
@@ -11,15 +27,13 @@ import com.android.systemui.Gefingerpoken
import com.android.wm.shell.animation.physicsAnimator
/**
- * A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful
- * when only measuring children but not the parent, when trying to apply a new scroll position
+ * A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful when
+ * only measuring children but not the parent, when trying to apply a new scroll position
*/
-class MediaScrollView @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0
-)
- : HorizontalScrollView(context, attrs, defStyleAttr) {
+class MediaScrollView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ HorizontalScrollView(context, attrs, defStyleAttr) {
lateinit var contentContainer: ViewGroup
private set
@@ -34,35 +48,33 @@ class MediaScrollView @JvmOverloads constructor(
* Get the current content translation. This is usually the normal translationX of the content,
* but when animating, it might differ
*/
- fun getContentTranslation() = if (contentContainer.physicsAnimator.isRunning()) {
- animationTargetX
- } else {
- contentContainer.translationX
- }
+ fun getContentTranslation() =
+ if (contentContainer.physicsAnimator.isRunning()) {
+ animationTargetX
+ } else {
+ contentContainer.translationX
+ }
/**
* Convert between the absolute (left-to-right) and relative (start-to-end) scrollX of the media
- * carousel. The player indices are always relative (start-to-end) and the scrollView.scrollX
- * is always absolute. This function is its own inverse.
+ * carousel. The player indices are always relative (start-to-end) and the scrollView.scrollX is
+ * always absolute. This function is its own inverse.
*/
- private fun transformScrollX(scrollX: Int): Int = if (isLayoutRtl) {
- contentContainer.width - width - scrollX
- } else {
- scrollX
- }
+ private fun transformScrollX(scrollX: Int): Int =
+ if (isLayoutRtl) {
+ contentContainer.width - width - scrollX
+ } else {
+ scrollX
+ }
- /**
- * Get the layoutDirection-relative (start-to-end) scroll X position of the carousel.
- */
+ /** Get the layoutDirection-relative (start-to-end) scroll X position of the carousel. */
var relativeScrollX: Int
get() = transformScrollX(scrollX)
set(value) {
scrollX = transformScrollX(value)
}
- /**
- * Allow all scrolls to go through, use base implementation
- */
+ /** Allow all scrolls to go through, use base implementation */
override fun scrollTo(x: Int, y: Int) {
if (mScrollX != x || mScrollY != y) {
val oldX: Int = mScrollX
@@ -79,17 +91,13 @@ class MediaScrollView @JvmOverloads constructor(
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
var intercept = false
- touchListener?.let {
- intercept = it.onInterceptTouchEvent(ev)
- }
+ touchListener?.let { intercept = it.onInterceptTouchEvent(ev) }
return super.onInterceptTouchEvent(ev) || intercept
}
override fun onTouchEvent(ev: MotionEvent?): Boolean {
var touch = false
- touchListener?.let {
- touch = it.onTouchEvent(ev)
- }
+ touchListener?.let { touch = it.onTouchEvent(ev) }
return super.onTouchEvent(ev) || touch
}
@@ -113,19 +121,25 @@ class MediaScrollView @JvmOverloads constructor(
// When we're dismissing we ignore all the scrolling
return false
}
- return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
- scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent)
+ return super.overScrollBy(
+ deltaX,
+ deltaY,
+ scrollX,
+ scrollY,
+ scrollRangeX,
+ scrollRangeY,
+ maxOverScrollX,
+ maxOverScrollY,
+ isTouchEvent
+ )
}
- /**
- * Cancel the current touch event going on.
- */
+ /** Cancel the current touch event going on. */
fun cancelCurrentScroll() {
val now = SystemClock.uptimeMillis()
- val event = MotionEvent.obtain(now, now,
- MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0)
+ val event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0)
event.source = InputDevice.SOURCE_TOUCHSCREEN
super.onTouchEvent(event)
event.recycle()
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index faa7aaee3c9a..4bf3031c02b4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -14,19 +14,22 @@
* limitations under the License
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.content.Context
import android.content.res.Configuration
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.R
-import com.android.systemui.media.MediaCarouselController.Companion.CONTROLS_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.DETAILS_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.DURATION
-import com.android.systemui.media.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.MEDIATITLES_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.calculateAlpha
+import com.android.systemui.media.controls.models.GutsViewHolder
+import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.CONTROLS_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DETAILS_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIATITLES_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.TransitionLayout
@@ -39,7 +42,9 @@ import javax.inject.Inject
* A class responsible for controlling a single instance of a media player handling interactions
* with the view instance and keeping the media view states up to date.
*/
-class MediaViewController @Inject constructor(
+class MediaViewController
+@Inject
+constructor(
private val context: Context,
private val configurationController: ConfigurationController,
private val mediaHostStatesManager: MediaHostStatesManager,
@@ -47,17 +52,18 @@ class MediaViewController @Inject constructor(
) {
/**
- * Indicating that the media view controller is for a notification-based player,
- * session-based player, or recommendation
+ * Indicating that the media view controller is for a notification-based player, session-based
+ * player, or recommendation
*/
enum class TYPE {
- PLAYER, RECOMMENDATION
+ PLAYER,
+ RECOMMENDATION
}
companion object {
- @JvmField
- val GUTS_ANIMATION_DURATION = 500L
- val controlIds = setOf(
+ @JvmField val GUTS_ANIMATION_DURATION = 500L
+ val controlIds =
+ setOf(
R.id.media_progress_bar,
R.id.actionNext,
R.id.actionPrev,
@@ -68,22 +74,20 @@ class MediaViewController @Inject constructor(
R.id.action4,
R.id.media_scrubbing_elapsed_time,
R.id.media_scrubbing_total_time
- )
+ )
- val detailIds = setOf(
+ val detailIds =
+ setOf(
R.id.header_title,
R.id.header_artist,
R.id.actionPlayPause,
- )
+ )
}
- /**
- * A listener when the current dimensions of the player change
- */
+ /** A listener when the current dimensions of the player change */
lateinit var sizeChangedListener: () -> Unit
private var firstRefresh: Boolean = true
- @VisibleForTesting
- private var transitionLayout: TransitionLayout? = null
+ @VisibleForTesting private var transitionLayout: TransitionLayout? = null
private val layoutController = TransitionLayoutController()
private var animationDelay: Long = 0
private var animationDuration: Long = 0
@@ -91,116 +95,98 @@ class MediaViewController @Inject constructor(
private val measurement = MeasurementOutput(0, 0)
private var type: TYPE = TYPE.PLAYER
- /**
- * A map containing all viewStates for all locations of this mediaState
- */
+ /** A map containing all viewStates for all locations of this mediaState */
private val viewStates: MutableMap<CacheKey, TransitionViewState?> = mutableMapOf()
/**
* The ending location of the view where it ends when all animations and transitions have
* finished
*/
- @MediaLocation
- var currentEndLocation: Int = -1
+ @MediaLocation var currentEndLocation: Int = -1
- /**
- * The starting location of the view where it starts for all animations and transitions
- */
- @MediaLocation
- private var currentStartLocation: Int = -1
+ /** The starting location of the view where it starts for all animations and transitions */
+ @MediaLocation private var currentStartLocation: Int = -1
- /**
- * The progress of the transition or 1.0 if there is no transition happening
- */
+ /** The progress of the transition or 1.0 if there is no transition happening */
private var currentTransitionProgress: Float = 1.0f
- /**
- * A temporary state used to store intermediate measurements.
- */
+ /** A temporary state used to store intermediate measurements. */
private val tmpState = TransitionViewState()
- /**
- * A temporary state used to store intermediate measurements.
- */
+ /** A temporary state used to store intermediate measurements. */
private val tmpState2 = TransitionViewState()
- /**
- * A temporary state used to store intermediate measurements.
- */
+ /** A temporary state used to store intermediate measurements. */
private val tmpState3 = TransitionViewState()
- /**
- * A temporary cache key to be used to look up cache entries
- */
+ /** A temporary cache key to be used to look up cache entries */
private val tmpKey = CacheKey()
/**
- * The current width of the player. This might not factor in case the player is animating
- * to the current state, but represents the end state
+ * The current width of the player. This might not factor in case the player is animating to the
+ * current state, but represents the end state
*/
var currentWidth: Int = 0
/**
- * The current height of the player. This might not factor in case the player is animating
- * to the current state, but represents the end state
+ * The current height of the player. This might not factor in case the player is animating to
+ * the current state, but represents the end state
*/
var currentHeight: Int = 0
- /**
- * Get the translationX of the layout
- */
+ /** Get the translationX of the layout */
var translationX: Float = 0.0f
private set
get() {
return transitionLayout?.translationX ?: 0.0f
}
- /**
- * Get the translationY of the layout
- */
+ /** Get the translationY of the layout */
var translationY: Float = 0.0f
private set
get() {
return transitionLayout?.translationY ?: 0.0f
}
- /**
- * A callback for RTL config changes
- */
- private val configurationListener = object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- // Because the TransitionLayout is not always attached (and calculates/caches layout
- // results regardless of attach state), we have to force the layoutDirection of the view
- // to the correct value for the user's current locale to ensure correct recalculation
- // when/after calling refreshState()
- newConfig?.apply {
- if (transitionLayout?.rawLayoutDirection != layoutDirection) {
- transitionLayout?.layoutDirection = layoutDirection
- refreshState()
+ /** A callback for RTL config changes */
+ private val configurationListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ // Because the TransitionLayout is not always attached (and calculates/caches layout
+ // results regardless of attach state), we have to force the layoutDirection of the
+ // view
+ // to the correct value for the user's current locale to ensure correct
+ // recalculation
+ // when/after calling refreshState()
+ newConfig?.apply {
+ if (transitionLayout?.rawLayoutDirection != layoutDirection) {
+ transitionLayout?.layoutDirection = layoutDirection
+ refreshState()
+ }
}
}
}
- }
- /**
- * A callback for media state changes
- */
- val stateCallback = object : MediaHostStatesManager.Callback {
- override fun onHostStateChanged(
- @MediaLocation location: Int,
- mediaHostState: MediaHostState
- ) {
- if (location == currentEndLocation || location == currentStartLocation) {
- setCurrentState(currentStartLocation,
+ /** A callback for media state changes */
+ val stateCallback =
+ object : MediaHostStatesManager.Callback {
+ override fun onHostStateChanged(
+ @MediaLocation location: Int,
+ mediaHostState: MediaHostState
+ ) {
+ if (location == currentEndLocation || location == currentStartLocation) {
+ setCurrentState(
+ currentStartLocation,
currentEndLocation,
currentTransitionProgress,
- applyImmediately = false)
+ applyImmediately = false
+ )
+ }
}
}
- }
/**
- * The expanded constraint set used to render a expanded player. If it is modified, make sure
- * to call [refreshState]
+ * The expanded constraint set used to render a expanded player. If it is modified, make sure to
+ * call [refreshState]
*/
val collapsedLayout = ConstraintSet()
@@ -210,9 +196,7 @@ class MediaViewController @Inject constructor(
*/
val expandedLayout = ConstraintSet()
- /**
- * Whether the guts are visible for the associated player.
- */
+ /** Whether the guts are visible for the associated player. */
var isGutsVisible = false
private set
@@ -234,17 +218,17 @@ class MediaViewController @Inject constructor(
configurationController.removeCallback(configurationListener)
}
- /**
- * Show guts with an animated transition.
- */
+ /** Show guts with an animated transition. */
fun openGuts() {
if (isGutsVisible) return
isGutsVisible = true
animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
- setCurrentState(currentStartLocation,
- currentEndLocation,
- currentTransitionProgress,
- applyImmediately = false)
+ setCurrentState(
+ currentStartLocation,
+ currentEndLocation,
+ currentTransitionProgress,
+ applyImmediately = false
+ )
}
/**
@@ -259,10 +243,12 @@ class MediaViewController @Inject constructor(
if (!immediate) {
animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
}
- setCurrentState(currentStartLocation,
- currentEndLocation,
- currentTransitionProgress,
- applyImmediately = immediate)
+ setCurrentState(
+ currentStartLocation,
+ currentEndLocation,
+ currentTransitionProgress,
+ applyImmediately = immediate
+ )
}
private fun ensureAllMeasurements() {
@@ -272,21 +258,20 @@ class MediaViewController @Inject constructor(
}
}
- /**
- * Get the constraintSet for a given expansion
- */
+ /** Get the constraintSet for a given expansion */
private fun constraintSetForExpansion(expansion: Float): ConstraintSet =
- if (expansion > 0) expandedLayout else collapsedLayout
+ if (expansion > 0) expandedLayout else collapsedLayout
/**
* Set the views to be showing/hidden based on the [isGutsVisible] for a given
* [TransitionViewState].
*/
private fun setGutsViewState(viewState: TransitionViewState) {
- val controlsIds = when (type) {
- TYPE.PLAYER -> MediaViewHolder.controlsIds
- TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds
- }
+ val controlsIds =
+ when (type) {
+ TYPE.PLAYER -> MediaViewHolder.controlsIds
+ TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds
+ }
val gutsIds = GutsViewHolder.ids
controlsIds.forEach { id ->
viewState.widgetStates.get(id)?.let { state ->
@@ -304,9 +289,7 @@ class MediaViewController @Inject constructor(
}
}
- /**
- * Apply squishFraction to a copy of viewState such that the cached version is untouched.
- */
+ /** Apply squishFraction to a copy of viewState such that the cached version is untouched. */
internal fun squishViewState(
viewState: TransitionViewState,
squishFraction: Float
@@ -344,8 +327,8 @@ class MediaViewController @Inject constructor(
* Obtain a new viewState for a given media state. This usually returns a cached state, but if
* it's not available, it will recreate one by measuring, which may be expensive.
*/
- @VisibleForTesting
- fun obtainViewState(state: MediaHostState?): TransitionViewState? {
+ @VisibleForTesting
+ fun obtainViewState(state: MediaHostState?): TransitionViewState? {
if (state == null || state.measurementInput == null) {
return null
}
@@ -368,10 +351,12 @@ class MediaViewController @Inject constructor(
}
// Let's create a new measurement
if (state.expansion == 0.0f || state.expansion == 1.0f) {
- result = transitionLayout!!.calculateViewState(
+ result =
+ transitionLayout!!.calculateViewState(
state.measurementInput!!,
constraintSetForExpansion(state.expansion),
- TransitionViewState())
+ TransitionViewState()
+ )
setGutsViewState(result)
// We don't want to cache interpolated or null states as this could quickly fill up
@@ -387,10 +372,8 @@ class MediaViewController @Inject constructor(
val startViewState = obtainViewState(startState) as TransitionViewState
val endState = state.copy().also { it.expansion = 1.0f }
val endViewState = obtainViewState(endState) as TransitionViewState
- result = layoutController.getInterpolatedState(
- startViewState,
- endViewState,
- state.expansion)
+ result =
+ layoutController.getInterpolatedState(startViewState, endViewState, state.expansion)
}
if (state.squishFraction <= 1f) {
return squishViewState(result, state.squishFraction)
@@ -398,11 +381,7 @@ class MediaViewController @Inject constructor(
return result
}
- private fun getKey(
- state: MediaHostState,
- guts: Boolean,
- result: CacheKey
- ): CacheKey {
+ private fun getKey(state: MediaHostState, guts: Boolean, result: CacheKey): CacheKey {
result.apply {
heightMeasureSpec = state.measurementInput?.heightMeasureSpec ?: 0
widthMeasureSpec = state.measurementInput?.widthMeasureSpec ?: 0
@@ -413,41 +392,39 @@ class MediaViewController @Inject constructor(
}
/**
- * Attach a view to this controller. This may perform measurements if it's not available yet
- * and should therefore be done carefully.
+ * Attach a view to this controller. This may perform measurements if it's not available yet and
+ * should therefore be done carefully.
*/
- fun attach(
- transitionLayout: TransitionLayout,
- type: TYPE
- ) = traceSection("MediaViewController#attach") {
- updateMediaViewControllerType(type)
- logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
- this.transitionLayout = transitionLayout
- layoutController.attach(transitionLayout)
- if (currentEndLocation == -1) {
- return
- }
- // Set the previously set state immediately to the view, now that it's finally attached
- setCurrentState(
+ fun attach(transitionLayout: TransitionLayout, type: TYPE) =
+ traceSection("MediaViewController#attach") {
+ updateMediaViewControllerType(type)
+ logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
+ this.transitionLayout = transitionLayout
+ layoutController.attach(transitionLayout)
+ if (currentEndLocation == -1) {
+ return
+ }
+ // Set the previously set state immediately to the view, now that it's finally attached
+ setCurrentState(
startLocation = currentStartLocation,
endLocation = currentEndLocation,
transitionProgress = currentTransitionProgress,
- applyImmediately = true)
- }
+ applyImmediately = true
+ )
+ }
/**
- * Obtain a measurement for a given location. This makes sure that the state is up to date
- * and all widgets know their location. Calling this method may create a measurement if we
- * don't have a cached value available already.
+ * Obtain a measurement for a given location. This makes sure that the state is up to date and
+ * all widgets know their location. Calling this method may create a measurement if we don't
+ * have a cached value available already.
*/
- fun getMeasurementsForState(
- hostState: MediaHostState
- ): MeasurementOutput? = traceSection("MediaViewController#getMeasurementsForState") {
- val viewState = obtainViewState(hostState) ?: return null
- measurement.measuredWidth = viewState.width
- measurement.measuredHeight = viewState.height
- return measurement
- }
+ fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? =
+ traceSection("MediaViewController#getMeasurementsForState") {
+ val viewState = obtainViewState(hostState) ?: return null
+ measurement.measuredWidth = viewState.width
+ measurement.measuredHeight = viewState.height
+ return measurement
+ }
/**
* Set a new state for the controlled view which can be an interpolation between multiple
@@ -458,67 +435,85 @@ class MediaViewController @Inject constructor(
@MediaLocation endLocation: Int,
transitionProgress: Float,
applyImmediately: Boolean
- ) = traceSection("MediaViewController#setCurrentState") {
- currentEndLocation = endLocation
- currentStartLocation = startLocation
- currentTransitionProgress = transitionProgress
- logger.logMediaLocation("setCurrentState", startLocation, endLocation)
-
- val shouldAnimate = animateNextStateChange && !applyImmediately
-
- val endHostState = mediaHostStatesManager.mediaHostStates[endLocation] ?: return
- val startHostState = mediaHostStatesManager.mediaHostStates[startLocation]
-
- // Obtain the view state that we'd want to be at the end
- // The view might not be bound yet or has never been measured and in that case will be
- // reset once the state is fully available
- var endViewState = obtainViewState(endHostState) ?: return
- endViewState = updateViewStateToCarouselSize(endViewState, endLocation, tmpState2)!!
- layoutController.setMeasureState(endViewState)
-
- // If the view isn't bound, we can drop the animation, otherwise we'll execute it
- animateNextStateChange = false
- if (transitionLayout == null) {
- return
- }
-
- val result: TransitionViewState
- var startViewState = obtainViewState(startHostState)
- startViewState = updateViewStateToCarouselSize(startViewState, startLocation, tmpState3)
+ ) =
+ traceSection("MediaViewController#setCurrentState") {
+ currentEndLocation = endLocation
+ currentStartLocation = startLocation
+ currentTransitionProgress = transitionProgress
+ logger.logMediaLocation("setCurrentState", startLocation, endLocation)
+
+ val shouldAnimate = animateNextStateChange && !applyImmediately
+
+ val endHostState = mediaHostStatesManager.mediaHostStates[endLocation] ?: return
+ val startHostState = mediaHostStatesManager.mediaHostStates[startLocation]
+
+ // Obtain the view state that we'd want to be at the end
+ // The view might not be bound yet or has never been measured and in that case will be
+ // reset once the state is fully available
+ var endViewState = obtainViewState(endHostState) ?: return
+ endViewState = updateViewStateToCarouselSize(endViewState, endLocation, tmpState2)!!
+ layoutController.setMeasureState(endViewState)
+
+ // If the view isn't bound, we can drop the animation, otherwise we'll execute it
+ animateNextStateChange = false
+ if (transitionLayout == null) {
+ return
+ }
- if (!endHostState.visible) {
- // Let's handle the case where the end is gone first. In this case we take the
- // start viewState and will make it gone
- if (startViewState == null || startHostState == null || !startHostState.visible) {
- // the start isn't a valid state, let's use the endstate directly
+ val result: TransitionViewState
+ var startViewState = obtainViewState(startHostState)
+ startViewState = updateViewStateToCarouselSize(startViewState, startLocation, tmpState3)
+
+ if (!endHostState.visible) {
+ // Let's handle the case where the end is gone first. In this case we take the
+ // start viewState and will make it gone
+ if (startViewState == null || startHostState == null || !startHostState.visible) {
+ // the start isn't a valid state, let's use the endstate directly
+ result = endViewState
+ } else {
+ // Let's get the gone presentation from the start state
+ result =
+ layoutController.getGoneState(
+ startViewState,
+ startHostState.disappearParameters,
+ transitionProgress,
+ tmpState
+ )
+ }
+ } else if (startHostState != null && !startHostState.visible) {
+ // We have a start state and it is gone.
+ // Let's get presentation from the endState
+ result =
+ layoutController.getGoneState(
+ endViewState,
+ endHostState.disappearParameters,
+ 1.0f - transitionProgress,
+ tmpState
+ )
+ } else if (transitionProgress == 1.0f || startViewState == null) {
+ // We're at the end. Let's use that state
result = endViewState
+ } else if (transitionProgress == 0.0f) {
+ // We're at the start. Let's use that state
+ result = startViewState
} else {
- // Let's get the gone presentation from the start state
- result = layoutController.getGoneState(startViewState,
- startHostState.disappearParameters,
+ result =
+ layoutController.getInterpolatedState(
+ startViewState,
+ endViewState,
transitionProgress,
- tmpState)
+ tmpState
+ )
}
- } else if (startHostState != null && !startHostState.visible) {
- // We have a start state and it is gone.
- // Let's get presentation from the endState
- result = layoutController.getGoneState(endViewState, endHostState.disappearParameters,
- 1.0f - transitionProgress,
- tmpState)
- } else if (transitionProgress == 1.0f || startViewState == null) {
- // We're at the end. Let's use that state
- result = endViewState
- } else if (transitionProgress == 0.0f) {
- // We're at the start. Let's use that state
- result = startViewState
- } else {
- result = layoutController.getInterpolatedState(startViewState, endViewState,
- transitionProgress, tmpState)
+ logger.logMediaSize("setCurrentState", result.width, result.height)
+ layoutController.setState(
+ result,
+ applyImmediately,
+ shouldAnimate,
+ animationDuration,
+ animationDelay
+ )
}
- logger.logMediaSize("setCurrentState", result.width, result.height)
- layoutController.setState(result, applyImmediately, shouldAnimate, animationDuration,
- animationDelay)
- }
private fun updateViewStateToCarouselSize(
viewState: TransitionViewState?,
@@ -555,8 +550,8 @@ class MediaViewController @Inject constructor(
}
/**
- * Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation].
- * In the event of [location] not being visible, [locationWhenHidden] will be used instead.
+ * Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation]. In the event
+ * of [location] not being visible, [locationWhenHidden] will be used instead.
*
* @param location Target
* @param locationWhenHidden Location that will be used when the target is not
@@ -573,40 +568,37 @@ class MediaViewController @Inject constructor(
* This updates the width the view will me measured with.
*/
fun onLocationPreChange(@MediaLocation newLocation: Int) {
- obtainViewStateForLocation(newLocation)?.let {
- layoutController.setMeasureState(it)
- }
+ obtainViewStateForLocation(newLocation)?.let { layoutController.setMeasureState(it) }
}
- /**
- * Request that the next state change should be animated with the given parameters.
- */
+ /** Request that the next state change should be animated with the given parameters. */
fun animatePendingStateChange(duration: Long, delay: Long) {
animateNextStateChange = true
animationDuration = duration
animationDelay = delay
}
- /**
- * Clear all existing measurements and refresh the state to match the view.
- */
- fun refreshState() = traceSection("MediaViewController#refreshState") {
- // Let's clear all of our measurements and recreate them!
- viewStates.clear()
- if (firstRefresh) {
- // This is the first bind, let's ensure we pre-cache all measurements. Otherwise
- // We'll just load these on demand.
- ensureAllMeasurements()
- firstRefresh = false
+ /** Clear all existing measurements and refresh the state to match the view. */
+ fun refreshState() =
+ traceSection("MediaViewController#refreshState") {
+ // Let's clear all of our measurements and recreate them!
+ viewStates.clear()
+ if (firstRefresh) {
+ // This is the first bind, let's ensure we pre-cache all measurements. Otherwise
+ // We'll just load these on demand.
+ ensureAllMeasurements()
+ firstRefresh = false
+ }
+ setCurrentState(
+ currentStartLocation,
+ currentEndLocation,
+ currentTransitionProgress,
+ applyImmediately = true
+ )
}
- setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress,
- applyImmediately = true)
- }
}
-/**
- * An internal key for the cache of mediaViewStates. This is a subset of the full host state.
- */
+/** An internal key for the cache of mediaViewStates. This is a subset of the full host state. */
private data class CacheKey(
var widthMeasureSpec: Int = -1,
var heightMeasureSpec: Int = -1,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
index 73868189b362..fdac33ac20b0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
@@ -14,50 +14,42 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaViewLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
private const val TAG = "MediaView"
-/**
- * A buffered log for media view events that are too noisy for regular logging
- */
+/** A buffered log for media view events that are too noisy for regular logging */
@SysUISingleton
-class MediaViewLogger @Inject constructor(
- @MediaViewLog private val buffer: LogBuffer
-) {
+class MediaViewLogger @Inject constructor(@MediaViewLog private val buffer: LogBuffer) {
fun logMediaSize(reason: String, width: Int, height: Int) {
buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = reason
- int1 = width
- int2 = height
- },
- {
- "size ($str1): $int1 x $int2"
- }
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = reason
+ int1 = width
+ int2 = height
+ },
+ { "size ($str1): $int1 x $int2" }
)
}
fun logMediaLocation(reason: String, startLocation: Int, endLocation: Int) {
buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = reason
- int1 = startLocation
- int2 = endLocation
- },
- {
- "location ($str1): $int1 -> $int2"
- }
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = reason
+ int1 = startLocation
+ int2 = endLocation
+ },
+ { "location ($str1): $int1 -> $int2" }
)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt
index 48f4a16cc538..1cdcf5ed2702 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -73,4 +73,4 @@ internal open class MetadataAnimationHandler(
exitAnimator.addListener(this)
enterAnimator.addListener(this)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index 6bc94cd5f525..e9b2cf2b18d1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.media
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.controls.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -23,8 +39,7 @@ import kotlin.math.cos
private const val TAG = "Squiggly"
private const val TWO_PI = (Math.PI * 2f).toFloat()
-@VisibleForTesting
-internal const val DISABLED_ALPHA = 77
+@VisibleForTesting internal const val DISABLED_ALPHA = 77
class SquigglyProgress : Drawable() {
@@ -86,26 +101,29 @@ class SquigglyProgress : Drawable() {
lastFrameTime = SystemClock.uptimeMillis()
}
heightAnimator?.cancel()
- heightAnimator = ValueAnimator.ofFloat(heightFraction, if (animate) 1f else 0f).apply {
- if (animate) {
- startDelay = 60
- duration = 800
- interpolator = Interpolators.EMPHASIZED_DECELERATE
- } else {
- duration = 550
- interpolator = Interpolators.STANDARD_DECELERATE
- }
- addUpdateListener {
- heightFraction = it.animatedValue as Float
- invalidateSelf()
- }
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- heightAnimator = null
+ heightAnimator =
+ ValueAnimator.ofFloat(heightFraction, if (animate) 1f else 0f).apply {
+ if (animate) {
+ startDelay = 60
+ duration = 800
+ interpolator = Interpolators.EMPHASIZED_DECELERATE
+ } else {
+ duration = 550
+ interpolator = Interpolators.STANDARD_DECELERATE
}
- })
- start()
- }
+ addUpdateListener {
+ heightFraction = it.animatedValue as Float
+ invalidateSelf()
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ heightAnimator = null
+ }
+ }
+ )
+ start()
+ }
}
override fun draw(canvas: Canvas) {
@@ -120,9 +138,15 @@ class SquigglyProgress : Drawable() {
val progress = level / 10_000f
val totalWidth = bounds.width().toFloat()
val totalProgressPx = totalWidth * progress
- val waveProgressPx = totalWidth * (
- if (!transitionEnabled || progress > matchedWaveEndpoint) progress else
- lerp(minWaveEndpoint, matchedWaveEndpoint, lerpInv(0f, matchedWaveEndpoint, progress)))
+ val waveProgressPx =
+ totalWidth *
+ (if (!transitionEnabled || progress > matchedWaveEndpoint) progress
+ else
+ lerp(
+ minWaveEndpoint,
+ matchedWaveEndpoint,
+ lerpInv(0f, matchedWaveEndpoint, progress)
+ ))
// Build Wiggly Path
val waveStart = -phaseOffset - waveLength / 2f
@@ -132,10 +156,8 @@ class SquigglyProgress : Drawable() {
val computeAmplitude: (Float, Float) -> Float = { x, sign ->
if (transitionEnabled) {
val length = transitionPeriods * waveLength
- val coeff = lerpInvSat(
- waveProgressPx + length / 2f,
- waveProgressPx - length / 2f,
- x)
+ val coeff =
+ lerpInvSat(waveProgressPx + length / 2f, waveProgressPx - length / 2f, x)
sign * heightFraction * lineAmplitude * coeff
} else {
sign * heightFraction * lineAmplitude
@@ -156,10 +178,7 @@ class SquigglyProgress : Drawable() {
val nextX = currentX + dist
val midX = currentX + dist / 2
val nextAmp = computeAmplitude(nextX, waveSign)
- path.cubicTo(
- midX, currentAmp,
- midX, nextAmp,
- nextX, nextAmp)
+ path.cubicTo(midX, currentAmp, midX, nextAmp, nextX, nextAmp)
currentAmp = nextAmp
currentX = nextX
}
@@ -229,7 +248,7 @@ class SquigglyProgress : Drawable() {
private fun updateColors(tintColor: Int, alpha: Int) {
wavePaint.color = ColorUtils.setAlphaComponent(tintColor, alpha)
- linePaint.color = ColorUtils.setAlphaComponent(tintColor,
- (DISABLED_ALPHA * (alpha / 255f)).toInt())
+ linePaint.color =
+ ColorUtils.setAlphaComponent(tintColor, (DISABLED_ALPHA * (alpha / 255f)).toInt())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
index ed3e10939b6a..6caf5c20b81c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media;
+package com.android.systemui.media.controls.util;
import android.annotation.NonNull;
import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
index b8185b9de7e8..bcfceaa3205e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
@@ -14,15 +14,25 @@
* limitations under the License.
*/
-package com.android.systemui.media;
+package com.android.systemui.media.controls.util;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.text.TextUtils;
+/**
+ * Utility class with common methods for media controls
+ */
public class MediaDataUtils {
+ /**
+ * Get the application label for a given package
+ * @param context the context to use
+ * @param packageName Package to check
+ * @param unknownName Fallback string if application is not found
+ * @return The label or fallback string
+ */
public static String getAppLabel(Context context, String packageName, String unknownName) {
if (TextUtils.isEmpty(packageName)) {
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFeatureFlag.kt
index 75eb33da64d8..91dac6f1a528 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFeatureFlag.kt
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.util
import android.content.Context
import com.android.systemui.util.Utils
import javax.inject.Inject
-/**
- * Provides access to the current value of the feature flag.
- */
+/** Provides access to the current value of the feature flag. */
class MediaFeatureFlag @Inject constructor(private val context: Context) {
val enabled
get() = Utils.useQsMediaPlayer(context)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index b85ae4820d49..8d4931a5d08c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.util
import android.app.StatusBarManager
import android.os.UserHandle
@@ -34,9 +34,7 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
return enabled || featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
}
- /**
- * Check whether we support displaying information about mute await connections.
- */
+ /** Check whether we support displaying information about mute await connections. */
fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
index 0baf01e7476f..3ad8c21e8a1e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.util
import com.android.internal.logging.InstanceId
import com.android.internal.logging.InstanceIdSequence
@@ -22,22 +22,21 @@ import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.MediaLocation
import java.lang.IllegalArgumentException
import javax.inject.Inject
private const val INSTANCE_ID_MAX = 1 shl 20
-/**
- * A helper class to log events related to the media controls
- */
+/** A helper class to log events related to the media controls */
@SysUISingleton
class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) {
private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
- /**
- * Get a new instance ID for a new media control
- */
+ /** Get a new instance ID for a new media control */
fun getNewInstanceId(): InstanceId {
return instanceIdSequence.newInstanceId()
}
@@ -48,12 +47,13 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
instanceId: InstanceId,
playbackLocation: Int
) {
- val event = when (playbackLocation) {
- MediaData.PLAYBACK_LOCAL -> MediaUiEvent.LOCAL_MEDIA_ADDED
- MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.CAST_MEDIA_ADDED
- MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.REMOTE_MEDIA_ADDED
- else -> throw IllegalArgumentException("Unknown playback location")
- }
+ val event =
+ when (playbackLocation) {
+ MediaData.PLAYBACK_LOCAL -> MediaUiEvent.LOCAL_MEDIA_ADDED
+ MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.CAST_MEDIA_ADDED
+ MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.REMOTE_MEDIA_ADDED
+ else -> throw IllegalArgumentException("Unknown playback location")
+ }
logger.logWithInstanceId(event, uid, packageName, instanceId)
}
@@ -63,12 +63,13 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
instanceId: InstanceId,
playbackLocation: Int
) {
- val event = when (playbackLocation) {
- MediaData.PLAYBACK_LOCAL -> MediaUiEvent.TRANSFER_TO_LOCAL
- MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.TRANSFER_TO_CAST
- MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.TRANSFER_TO_REMOTE
- else -> throw IllegalArgumentException("Unknown playback location")
- }
+ val event =
+ when (playbackLocation) {
+ MediaData.PLAYBACK_LOCAL -> MediaUiEvent.TRANSFER_TO_LOCAL
+ MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.TRANSFER_TO_CAST
+ MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.TRANSFER_TO_REMOTE
+ else -> throw IllegalArgumentException("Unknown playback location")
+ }
logger.logWithInstanceId(event, uid, packageName, instanceId)
}
@@ -107,8 +108,12 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
}
fun logLongPressSettings(uid: Int, packageName: String, instanceId: InstanceId) {
- logger.logWithInstanceId(MediaUiEvent.OPEN_SETTINGS_LONG_PRESS, uid, packageName,
- instanceId)
+ logger.logWithInstanceId(
+ MediaUiEvent.OPEN_SETTINGS_LONG_PRESS,
+ uid,
+ packageName,
+ instanceId
+ )
}
fun logCarouselSettings() {
@@ -117,12 +122,13 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
}
fun logTapAction(buttonId: Int, uid: Int, packageName: String, instanceId: InstanceId) {
- val event = when (buttonId) {
- R.id.actionPlayPause -> MediaUiEvent.TAP_ACTION_PLAY_PAUSE
- R.id.actionPrev -> MediaUiEvent.TAP_ACTION_PREV
- R.id.actionNext -> MediaUiEvent.TAP_ACTION_NEXT
- else -> MediaUiEvent.TAP_ACTION_OTHER
- }
+ val event =
+ when (buttonId) {
+ R.id.actionPlayPause -> MediaUiEvent.TAP_ACTION_PLAY_PAUSE
+ R.id.actionPrev -> MediaUiEvent.TAP_ACTION_PREV
+ R.id.actionNext -> MediaUiEvent.TAP_ACTION_NEXT
+ else -> MediaUiEvent.TAP_ACTION_OTHER
+ }
logger.logWithInstanceId(event, uid, packageName, instanceId)
}
@@ -140,148 +146,130 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
}
fun logCarouselPosition(@MediaLocation location: Int) {
- val event = when (location) {
- MediaHierarchyManager.LOCATION_QQS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QQS
- MediaHierarchyManager.LOCATION_QS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QS
- MediaHierarchyManager.LOCATION_LOCKSCREEN ->
- MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN
- MediaHierarchyManager.LOCATION_DREAM_OVERLAY ->
- MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM
- else -> throw IllegalArgumentException("Unknown media carousel location $location")
- }
+ val event =
+ when (location) {
+ MediaHierarchyManager.LOCATION_QQS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QQS
+ MediaHierarchyManager.LOCATION_QS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QS
+ MediaHierarchyManager.LOCATION_LOCKSCREEN ->
+ MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN
+ MediaHierarchyManager.LOCATION_DREAM_OVERLAY ->
+ MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM
+ else -> throw IllegalArgumentException("Unknown media carousel location $location")
+ }
logger.log(event)
}
fun logRecommendationAdded(packageName: String, instanceId: InstanceId) {
- logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_ADDED, 0, packageName,
- instanceId)
+ logger.logWithInstanceId(
+ MediaUiEvent.MEDIA_RECOMMENDATION_ADDED,
+ 0,
+ packageName,
+ instanceId
+ )
}
fun logRecommendationRemoved(packageName: String, instanceId: InstanceId) {
- logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_REMOVED, 0, packageName,
- instanceId)
+ logger.logWithInstanceId(
+ MediaUiEvent.MEDIA_RECOMMENDATION_REMOVED,
+ 0,
+ packageName,
+ instanceId
+ )
}
fun logRecommendationActivated(uid: Int, packageName: String, instanceId: InstanceId) {
- logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_ACTIVATED, uid, packageName,
- instanceId)
+ logger.logWithInstanceId(
+ MediaUiEvent.MEDIA_RECOMMENDATION_ACTIVATED,
+ uid,
+ packageName,
+ instanceId
+ )
}
fun logRecommendationItemTap(packageName: String, instanceId: InstanceId, position: Int) {
- logger.logWithInstanceIdAndPosition(MediaUiEvent.MEDIA_RECOMMENDATION_ITEM_TAP, 0,
- packageName, instanceId, position)
+ logger.logWithInstanceIdAndPosition(
+ MediaUiEvent.MEDIA_RECOMMENDATION_ITEM_TAP,
+ 0,
+ packageName,
+ instanceId,
+ position
+ )
}
fun logRecommendationCardTap(packageName: String, instanceId: InstanceId) {
- logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_CARD_TAP, 0, packageName,
- instanceId)
+ 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)
+ logger.logWithInstanceId(
+ MediaUiEvent.MEDIA_OPEN_BROADCAST_DIALOG,
+ uid,
+ packageName,
+ instanceId
+ )
}
}
enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@UiEvent(doc = "A new media control was added for media playing locally on the device")
LOCAL_MEDIA_ADDED(1029),
-
@UiEvent(doc = "A new media control was added for media cast from the device")
CAST_MEDIA_ADDED(1030),
-
@UiEvent(doc = "A new media control was added for media playing remotely")
REMOTE_MEDIA_ADDED(1031),
-
@UiEvent(doc = "The media for an existing control was transferred to local playback")
TRANSFER_TO_LOCAL(1032),
-
@UiEvent(doc = "The media for an existing control was transferred to a cast device")
TRANSFER_TO_CAST(1033),
-
@UiEvent(doc = "The media for an existing control was transferred to a remote device")
TRANSFER_TO_REMOTE(1034),
-
- @UiEvent(doc = "A new resumable media control was added")
- RESUME_MEDIA_ADDED(1013),
-
+ @UiEvent(doc = "A new resumable media control was added") RESUME_MEDIA_ADDED(1013),
@UiEvent(doc = "An existing active media control was converted into resumable media")
ACTIVE_TO_RESUME(1014),
-
- @UiEvent(doc = "A media control timed out")
- MEDIA_TIMEOUT(1015),
-
- @UiEvent(doc = "A media control was removed from the carousel")
- MEDIA_REMOVED(1016),
-
- @UiEvent(doc = "User swiped to another control within the media carousel")
- CAROUSEL_PAGE(1017),
-
- @UiEvent(doc = "The user swiped away the media carousel")
- DISMISS_SWIPE(1018),
-
- @UiEvent(doc = "The user long pressed on a media control")
- OPEN_LONG_PRESS(1019),
-
+ @UiEvent(doc = "A media control timed out") MEDIA_TIMEOUT(1015),
+ @UiEvent(doc = "A media control was removed from the carousel") MEDIA_REMOVED(1016),
+ @UiEvent(doc = "User swiped to another control within the media carousel") CAROUSEL_PAGE(1017),
+ @UiEvent(doc = "The user swiped away the media carousel") DISMISS_SWIPE(1018),
+ @UiEvent(doc = "The user long pressed on a media control") OPEN_LONG_PRESS(1019),
@UiEvent(doc = "The user dismissed a media control via its long press menu")
DISMISS_LONG_PRESS(1020),
-
@UiEvent(doc = "The user opened media settings from a media control's long press menu")
OPEN_SETTINGS_LONG_PRESS(1021),
-
@UiEvent(doc = "The user opened media settings from the media carousel")
OPEN_SETTINGS_CAROUSEL(1022),
-
@UiEvent(doc = "The play/pause button on a media control was tapped")
TAP_ACTION_PLAY_PAUSE(1023),
-
- @UiEvent(doc = "The previous button on a media control was tapped")
- TAP_ACTION_PREV(1024),
-
- @UiEvent(doc = "The next button on a media control was tapped")
- TAP_ACTION_NEXT(1025),
-
+ @UiEvent(doc = "The previous button on a media control was tapped") TAP_ACTION_PREV(1024),
+ @UiEvent(doc = "The next button on a media control was tapped") TAP_ACTION_NEXT(1025),
@UiEvent(doc = "A custom or generic action button on a media control was tapped")
TAP_ACTION_OTHER(1026),
-
- @UiEvent(doc = "The user seeked on a media control using the seekbar")
- ACTION_SEEK(1027),
-
+ @UiEvent(doc = "The user seeked on a media control using the seekbar") ACTION_SEEK(1027),
@UiEvent(doc = "The user opened the output switcher from a media control")
OPEN_OUTPUT_SWITCHER(1028),
-
- @UiEvent(doc = "The user tapped on a media control view")
- MEDIA_TAP_CONTENT_VIEW(1036),
-
- @UiEvent(doc = "The media carousel moved to QQS")
- MEDIA_CAROUSEL_LOCATION_QQS(1037),
-
- @UiEvent(doc = "THe media carousel moved to QS")
- MEDIA_CAROUSEL_LOCATION_QS(1038),
-
+ @UiEvent(doc = "The user tapped on a media control view") MEDIA_TAP_CONTENT_VIEW(1036),
+ @UiEvent(doc = "The media carousel moved to QQS") MEDIA_CAROUSEL_LOCATION_QQS(1037),
+ @UiEvent(doc = "THe media carousel moved to QS") MEDIA_CAROUSEL_LOCATION_QS(1038),
@UiEvent(doc = "The media carousel moved to the lockscreen")
MEDIA_CAROUSEL_LOCATION_LOCKSCREEN(1039),
-
@UiEvent(doc = "The media carousel moved to the dream state")
MEDIA_CAROUSEL_LOCATION_DREAM(1040),
-
@UiEvent(doc = "A media recommendation card was added to the media carousel")
MEDIA_RECOMMENDATION_ADDED(1041),
-
@UiEvent(doc = "A media recommendation card was removed from the media carousel")
MEDIA_RECOMMENDATION_REMOVED(1042),
-
@UiEvent(doc = "An existing media control was made active as a recommendation")
MEDIA_RECOMMENDATION_ACTIVATED(1043),
-
@UiEvent(doc = "User tapped on an item in a media recommendation card")
MEDIA_RECOMMENDATION_ITEM_TAP(1044),
-
@UiEvent(doc = "User tapped on a media recommendation card")
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/SmallHash.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/SmallHash.java
index de7aac609955..97483a61baa4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmallHash.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/SmallHash.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media;
+package com.android.systemui.media.controls.util;
import java.util.Objects;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 66c036cee600..3e5d337bff9d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -17,24 +17,22 @@
package com.android.systemui.media.dagger;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.dagger.MediaTttReceiverLogBuffer;
import com.android.systemui.log.dagger.MediaTttSenderLogBuffer;
-import com.android.systemui.media.MediaDataManager;
-import com.android.systemui.media.MediaFlags;
-import com.android.systemui.media.MediaHierarchyManager;
-import com.android.systemui.media.MediaHost;
-import com.android.systemui.media.MediaHostStatesManager;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.MediaHostStatesManager;
+import com.android.systemui.media.controls.util.MediaFlags;
import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
-import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;
-import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
+import com.android.systemui.plugins.log.LogBuffer;
import java.util.Optional;
@@ -94,30 +92,6 @@ public interface MediaModule {
return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
}
- /** */
- @Provides
- @SysUISingleton
- static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender(
- MediaTttFlags mediaTttFlags,
- Lazy<MediaTttChipControllerSender> controllerSenderLazy) {
- if (!mediaTttFlags.isMediaTttEnabled()) {
- return Optional.empty();
- }
- return Optional.of(controllerSenderLazy.get());
- }
-
- /** */
- @Provides
- @SysUISingleton
- static Optional<MediaTttChipControllerReceiver> providesMediaTttChipControllerReceiver(
- MediaTttFlags mediaTttFlags,
- Lazy<MediaTttChipControllerReceiver> controllerReceiverLazy) {
- if (!mediaTttFlags.isMediaTttEnabled()) {
- return Optional.empty();
- }
- return Optional.of(controllerReceiverLazy.get());
- }
-
@Provides
@SysUISingleton
@MediaTttSenderLogger
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
index 65c5bc76f3c5..69b5698b9042 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
@@ -21,9 +21,9 @@ import static com.android.systemui.media.dream.dagger.MediaComplicationComponent
import android.widget.FrameLayout;
-import com.android.systemui.media.MediaHierarchyManager;
-import com.android.systemui.media.MediaHost;
-import com.android.systemui.media.MediaHostState;
+import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.MediaHostState;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index 91e7b4933096..20e8ae6719f3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -27,9 +27,9 @@ import com.android.systemui.CoreStartable;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.DreamMediaEntryComplication;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaData;
-import com.android.systemui.media.MediaDataManager;
-import com.android.systemui.media.SmartspaceMediaData;
+import com.android.systemui.media.controls.models.player.MediaData;
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
index ffcc1f75f077..e26089450c21 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
@@ -21,7 +21,7 @@ import com.android.settingslib.media.DeviceIconUtil
import com.android.settingslib.media.LocalMediaManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.MediaFlags
+import com.android.systemui.media.controls.util.MediaFlags
import java.util.concurrent.Executor
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
index 78f4e012da03..5ace3ea8a05b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
@@ -1,9 +1,9 @@
package com.android.systemui.media.muteawait
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaMuteAwaitLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** Log messages for [MediaMuteAwaitConnectionManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
index 46b2cc141b3c..78408fce5a36 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
@@ -1,9 +1,9 @@
package com.android.systemui.media.nearby
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NearbyMediaDevicesLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** Log messages for [NearbyMediaDevicesManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md
index 6379960b85e9..b5a0483e0c69 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/README.md
@@ -41,3 +41,5 @@ in the `receiver` package, and code that's shared between them is in the `common
## Testing
If you want to test out the tap-to-transfer chip without using the `@SystemApi`s, you can use adb
commands instead. Refer to `MediaTttCommandLineHelper` for information about adb commands.
+
+TODO(b/245610654): Update this page once the chipbar migration is complete.
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index b565f3c22f24..38c971ed3f7d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -16,8 +16,8 @@
package com.android.systemui.media.taptotransfer.common
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.temporarydisplay.TemporaryViewLogger
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index c3de94f28aea..0a6043793ef6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -21,6 +21,8 @@ import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import com.android.settingslib.Utils
import com.android.systemui.R
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
/** Utility methods for media tap-to-transfer. */
class MediaTttUtils {
@@ -31,6 +33,23 @@ class MediaTttUtils {
const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED"
/**
+ * Returns the information needed to display the icon in [Icon] form.
+ *
+ * See [getIconInfoFromPackageName].
+ */
+ fun getIconFromPackageName(
+ context: Context,
+ appPackageName: String?,
+ logger: MediaTttLogger,
+ ): Icon {
+ val iconInfo = getIconInfoFromPackageName(context, appPackageName, logger)
+ return Icon.Loaded(
+ iconInfo.drawable,
+ ContentDescription.Loaded(iconInfo.contentDescription)
+ )
+ }
+
+ /**
* Returns the information needed to display the icon.
*
* The information will either contain app name and icon of the app playing media, or a
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 1461293c7a07..089625ca8d9c 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
@@ -36,6 +36,7 @@ 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.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
@@ -52,6 +53,8 @@ import javax.inject.Inject
* A controller to display and hide the Media Tap-To-Transfer chip on the **receiving** device.
*
* This chip is shown when a user is transferring media to/from a sending device and this device.
+ *
+ * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator.
*/
@SysUISingleton
class MediaTttChipControllerReceiver @Inject constructor(
@@ -64,6 +67,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
configurationController: ConfigurationController,
powerManager: PowerManager,
@Main private val mainHandler: Handler,
+ private val mediaTttFlags: MediaTttFlags,
private val uiEventLogger: MediaTttReceiverUiEventLogger,
private val viewUtil: ViewUtil,
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
@@ -118,7 +122,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
uiEventLogger.logReceiverStateChange(chipState)
if (chipState == ChipStateReceiver.FAR_FROM_SENDER) {
- removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER::class.simpleName!!)
+ removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER.name)
return
}
if (appIcon == null) {
@@ -138,7 +142,9 @@ class MediaTttChipControllerReceiver @Inject constructor(
}
override fun start() {
- commandQueue.addCallback(commandQueueCallbacks)
+ if (mediaTttFlags.isMediaTttEnabled()) {
+ commandQueue.addCallback(commandQueueCallbacks)
+ }
}
override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index aae973dcc1c7..6e596ee1f473 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -18,14 +18,11 @@ package com.android.systemui.media.taptotransfer.sender
import android.app.StatusBarManager
import android.content.Context
-import android.media.MediaRoute2Info
import android.util.Log
-import android.view.View
import androidx.annotation.StringRes
import com.android.internal.logging.UiEventLogger
-import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
-import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.common.shared.model.Text
import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
/**
@@ -36,6 +33,7 @@ import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
* @property stringResId the res ID of the string that should be displayed in the chip. Null if the
* state should not have the chip be displayed.
* @property transferStatus the transfer status that the chip state represents.
+ * @property endItem the item that should be displayed in the end section of the chip.
* @property timeout the amount of time this chip should display on the screen before it times out
* and disappears.
*/
@@ -44,6 +42,7 @@ enum class ChipStateSender(
val uiEvent: UiEventLogger.UiEventEnum,
@StringRes val stringResId: Int?,
val transferStatus: TransferStatus,
+ val endItem: SenderEndItem?,
val timeout: Long = DEFAULT_TIMEOUT_MILLIS
) {
/**
@@ -56,6 +55,7 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST,
R.string.media_move_closer_to_start_cast,
transferStatus = TransferStatus.NOT_STARTED,
+ endItem = null,
),
/**
@@ -69,6 +69,7 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST,
R.string.media_move_closer_to_end_cast,
transferStatus = TransferStatus.NOT_STARTED,
+ endItem = null,
),
/**
@@ -80,6 +81,7 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED,
R.string.media_transfer_playing_different_device,
transferStatus = TransferStatus.IN_PROGRESS,
+ endItem = SenderEndItem.Loading,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
),
@@ -92,6 +94,7 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
R.string.media_transfer_playing_this_device,
transferStatus = TransferStatus.IN_PROGRESS,
+ endItem = SenderEndItem.Loading,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
),
@@ -103,36 +106,13 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED,
R.string.media_transfer_playing_different_device,
transferStatus = TransferStatus.SUCCEEDED,
- ) {
- override fun undoClickListener(
- controllerSender: MediaTttChipControllerSender,
- routeInfo: MediaRoute2Info,
- undoCallback: IUndoMediaTransferCallback?,
- uiEventLogger: MediaTttSenderUiEventLogger,
- falsingManager: FalsingManager,
- ): View.OnClickListener? {
- if (undoCallback == null) {
- return null
- }
- return View.OnClickListener {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
-
- uiEventLogger.logUndoClicked(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED
- )
- undoCallback.onUndoTriggered()
- // The external service should eventually send us a TransferToThisDeviceTriggered
- // state, but that may take too long to go through the binder and the user may be
- // confused ast o why the UI hasn't changed yet. So, we immediately change the UI
- // here.
- controllerSender.displayView(
- ChipSenderInfo(
- TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo, undoCallback
- )
- )
- }
- }
- },
+ endItem = SenderEndItem.UndoButton(
+ uiEventOnClick =
+ MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED,
+ newState =
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ ),
+ ),
/**
* A state representing that a transfer back to this device has been successfully completed.
@@ -142,36 +122,13 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
R.string.media_transfer_playing_this_device,
transferStatus = TransferStatus.SUCCEEDED,
- ) {
- override fun undoClickListener(
- controllerSender: MediaTttChipControllerSender,
- routeInfo: MediaRoute2Info,
- undoCallback: IUndoMediaTransferCallback?,
- uiEventLogger: MediaTttSenderUiEventLogger,
- falsingManager: FalsingManager,
- ): View.OnClickListener? {
- if (undoCallback == null) {
- return null
- }
- return View.OnClickListener {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
-
- uiEventLogger.logUndoClicked(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED
- )
- undoCallback.onUndoTriggered()
- // The external service should eventually send us a TransferToReceiverTriggered
- // state, but that may take too long to go through the binder and the user may be
- // confused as to why the UI hasn't changed yet. So, we immediately change the UI
- // here.
- controllerSender.displayView(
- ChipSenderInfo(
- TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo, undoCallback
- )
- )
- }
- }
- },
+ endItem = SenderEndItem.UndoButton(
+ uiEventOnClick =
+ MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED,
+ newState =
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED
+ ),
+ ),
/** A state representing that a transfer to the receiver device has failed. */
TRANSFER_TO_RECEIVER_FAILED(
@@ -179,6 +136,7 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED,
R.string.media_transfer_failed,
transferStatus = TransferStatus.FAILED,
+ endItem = SenderEndItem.Error,
),
/** A state representing that a transfer back to this device has failed. */
@@ -187,6 +145,7 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED,
R.string.media_transfer_failed,
transferStatus = TransferStatus.FAILED,
+ endItem = SenderEndItem.Error,
),
/** A state representing that this device is far away from any receiver device. */
@@ -195,37 +154,27 @@ enum class ChipStateSender(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER,
stringResId = null,
transferStatus = TransferStatus.TOO_FAR,
- );
+ // We shouldn't be displaying the chipbar anyway
+ endItem = null,
+ ) {
+ override fun getChipTextString(context: Context, otherDeviceName: String): Text {
+ // TODO(b/245610654): Better way to handle this.
+ throw IllegalArgumentException("FAR_FROM_RECEIVER should never be displayed, " +
+ "so its string should never be fetched")
+ }
+ };
/**
* Returns a fully-formed string with the text that the chip should display.
*
+ * Throws an NPE if [stringResId] is null.
+ *
* @param otherDeviceName the name of the other device involved in the transfer.
*/
- fun getChipTextString(context: Context, otherDeviceName: String): String? {
- if (stringResId == null) {
- return null
- }
- return context.getString(stringResId, otherDeviceName)
+ open fun getChipTextString(context: Context, otherDeviceName: String): Text {
+ return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
}
- /**
- * Returns a click listener for the undo button on the chip. Returns null if this chip state
- * doesn't have an undo button.
- *
- * @param controllerSender passed as a parameter in case we want to display a new chip state
- * when undo is clicked.
- * @param undoCallback if present, the callback that should be called when the user clicks the
- * undo button. The undo button will only be shown if this is non-null.
- */
- open fun undoClickListener(
- controllerSender: MediaTttChipControllerSender,
- routeInfo: MediaRoute2Info,
- undoCallback: IUndoMediaTransferCallback?,
- uiEventLogger: MediaTttSenderUiEventLogger,
- falsingManager: FalsingManager,
- ): View.OnClickListener? = null
-
companion object {
/**
* Returns the sender state enum associated with the given [displayState] from
@@ -251,6 +200,26 @@ enum class ChipStateSender(
}
}
+/** Represents the item that should be displayed in the end section of the chip. */
+sealed class SenderEndItem {
+ /** A loading icon should be displayed. */
+ object Loading : SenderEndItem()
+
+ /** An error icon should be displayed. */
+ object Error : SenderEndItem()
+
+ /**
+ * An undo button should be displayed.
+ *
+ * @property uiEventOnClick the UI event to log when this button is clicked.
+ * @property newState the state that should immediately be transitioned to.
+ */
+ data class UndoButton(
+ val uiEventOnClick: UiEventLogger.UiEventEnum,
+ @StatusBarManager.MediaTransferSenderState val newState: Int,
+ ) : SenderEndItem()
+}
+
// Give the Transfer*Triggered states a longer timeout since those states represent an active
// process and we should keep the user informed about it as long as possible (but don't allow it to
// continue indefinitely).
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
deleted file mode 100644
index 5d631450cb41..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ /dev/null
@@ -1,247 +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.media.taptotransfer.sender
-
-import android.app.StatusBarManager
-import android.content.Context
-import android.graphics.Rect
-import android.media.MediaRoute2Info
-import android.os.PowerManager
-import android.util.Log
-import android.view.Gravity
-import android.view.MotionEvent
-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.internal.widget.CachingIconView
-import com.android.systemui.Gefingerpoken
-import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.animation.ViewHierarchyAnimator
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.common.MediaTttUtils
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason
-import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.view.ViewUtil
-import javax.inject.Inject
-
-/**
- * A controller to display and hide the Media Tap-To-Transfer chip on the **sending** device. This
- * chip is shown when a user is transferring media to/from this device and a receiver device.
- */
-@SysUISingleton
-open class MediaTttChipControllerSender @Inject constructor(
- private val commandQueue: CommandQueue,
- context: Context,
- @MediaTttSenderLogger logger: MediaTttLogger,
- windowManager: WindowManager,
- @Main mainExecutor: DelayableExecutor,
- accessibilityManager: AccessibilityManager,
- configurationController: ConfigurationController,
- powerManager: PowerManager,
- private val uiEventLogger: MediaTttSenderUiEventLogger,
- private val falsingManager: FalsingManager,
- private val falsingCollector: FalsingCollector,
- private val viewUtil: ViewUtil,
-) : TemporaryViewDisplayController<ChipSenderInfo, MediaTttLogger>(
- context,
- logger,
- windowManager,
- mainExecutor,
- accessibilityManager,
- configurationController,
- powerManager,
- R.layout.media_ttt_chip,
- MediaTttUtils.WINDOW_TITLE,
- MediaTttUtils.WAKE_REASON,
-) {
-
- private lateinit var parent: MediaTttChipRootView
-
- override val windowLayoutParams = commonWindowLayoutParams.apply {
- gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
- }
-
- private val commandQueueCallbacks = object : CommandQueue.Callbacks {
- override fun updateMediaTapToTransferSenderDisplay(
- @StatusBarManager.MediaTransferSenderState displayState: Int,
- routeInfo: MediaRoute2Info,
- undoCallback: IUndoMediaTransferCallback?
- ) {
- this@MediaTttChipControllerSender.updateMediaTapToTransferSenderDisplay(
- displayState, routeInfo, undoCallback
- )
- }
- }
-
- private fun updateMediaTapToTransferSenderDisplay(
- @StatusBarManager.MediaTransferSenderState displayState: Int,
- routeInfo: MediaRoute2Info,
- undoCallback: IUndoMediaTransferCallback?
- ) {
- val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState)
- val stateName = chipState?.name ?: "Invalid"
- logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
-
- if (chipState == null) {
- Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState")
- return
- }
- uiEventLogger.logSenderStateChange(chipState)
-
- if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
- removeView(removalReason = ChipStateSender.FAR_FROM_RECEIVER.name)
- } else {
- displayView(ChipSenderInfo(chipState, routeInfo, undoCallback))
- }
- }
-
- override fun start() {
- commandQueue.addCallback(commandQueueCallbacks)
- }
-
- override fun updateView(
- newInfo: ChipSenderInfo,
- currentView: ViewGroup
- ) {
- val chipState = newInfo.state
-
- // Detect falsing touches on the chip.
- parent = currentView.requireViewById(R.id.media_ttt_sender_chip)
- parent.touchHandler = object : Gefingerpoken {
- override fun onTouchEvent(ev: MotionEvent?): Boolean {
- falsingCollector.onTouchEvent(ev)
- return false
- }
- }
-
- // App icon
- val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
- context, newInfo.routeInfo.clientPackageName, logger
- )
- val iconView = currentView.requireViewById<CachingIconView>(R.id.app_icon)
- iconView.setImageDrawable(iconInfo.drawable)
- iconView.contentDescription = iconInfo.contentDescription
-
- // Text
- val otherDeviceName = newInfo.routeInfo.name.toString()
- val chipText = chipState.getChipTextString(context, otherDeviceName)
- currentView.requireViewById<TextView>(R.id.text).text = chipText
-
- // Loading
- currentView.requireViewById<View>(R.id.loading).visibility =
- (chipState.transferStatus == TransferStatus.IN_PROGRESS).visibleIfTrue()
-
- // Undo
- val undoView = currentView.requireViewById<View>(R.id.undo)
- val undoClickListener = chipState.undoClickListener(
- this,
- newInfo.routeInfo,
- newInfo.undoCallback,
- uiEventLogger,
- falsingManager,
- )
- undoView.setOnClickListener(undoClickListener)
- undoView.visibility = (undoClickListener != null).visibleIfTrue()
-
- // Failure
- currentView.requireViewById<View>(R.id.failure_icon).visibility =
- (chipState.transferStatus == TransferStatus.FAILED).visibleIfTrue()
-
- // For accessibility
- currentView.requireViewById<ViewGroup>(
- R.id.media_ttt_sender_chip_inner
- ).contentDescription = "${iconInfo.contentDescription} $chipText"
- }
-
- override fun animateViewIn(view: ViewGroup) {
- val chipInnerView = view.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner)
- ViewHierarchyAnimator.animateAddition(
- chipInnerView,
- ViewHierarchyAnimator.Hotspot.TOP,
- Interpolators.EMPHASIZED_DECELERATE,
- duration = ANIMATION_DURATION,
- includeMargins = true,
- includeFadeIn = true,
- // We can only request focus once the animation finishes.
- onAnimationEnd = { chipInnerView.requestAccessibilityFocus() },
- )
- }
-
- override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
- ViewHierarchyAnimator.animateRemoval(
- view.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner),
- ViewHierarchyAnimator.Hotspot.TOP,
- Interpolators.EMPHASIZED_ACCELERATE,
- ANIMATION_DURATION,
- onAnimationEnd,
- )
- // TODO(b/203800644): Add includeMargins as an option to ViewHierarchyAnimator so that the
- // animateChipOut matches the animateChipIn.
- }
-
- override fun shouldIgnoreViewRemoval(info: ChipSenderInfo, removalReason: String): Boolean {
- // Don't remove the chip if we're in progress or succeeded, since the user should still be
- // able to see the status of the transfer. (But do remove it if it's finally timed out.)
- val transferStatus = info.state.transferStatus
- if (
- (transferStatus == TransferStatus.IN_PROGRESS ||
- transferStatus == TransferStatus.SUCCEEDED) &&
- removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT
- ) {
- logger.logRemovalBypass(
- removalReason, bypassReason = "transferStatus=${transferStatus.name}"
- )
- return true
- }
- return false
- }
-
- override fun getTouchableRegion(view: View, outRect: Rect) {
- viewUtil.setRectToViewWindowLocation(view, outRect)
- }
-
- private fun Boolean.visibleIfTrue(): Int {
- return if (this) {
- View.VISIBLE
- } else {
- View.GONE
- }
- }
-}
-
-data class ChipSenderInfo(
- val state: ChipStateSender,
- val routeInfo: MediaRoute2Info,
- val undoCallback: IUndoMediaTransferCallback? = null
-) : TemporaryViewInfo {
- override fun getTimeoutMs() = state.timeout
-}
-
-const val SENDER_TAG = "MediaTapToTransferSender"
-private const val ANIMATION_DURATION = 500L
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
new file mode 100644
index 000000000000..edf759ddfd22
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.taptotransfer.sender
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.media.MediaRoute2Info
+import android.util.Log
+import android.view.View
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.systemui.CoreStartable
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.taptotransfer.MediaTttFlags
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import com.android.systemui.temporarydisplay.chipbar.SENDER_TAG
+import javax.inject.Inject
+
+/**
+ * A coordinator for showing/hiding the Media Tap-To-Transfer UI on the **sending** device. This UI
+ * is shown when a user is transferring media to/from this device and a receiver device.
+ */
+@SysUISingleton
+class MediaTttSenderCoordinator
+@Inject
+constructor(
+ private val chipbarCoordinator: ChipbarCoordinator,
+ private val commandQueue: CommandQueue,
+ private val context: Context,
+ @MediaTttSenderLogger private val logger: MediaTttLogger,
+ private val mediaTttFlags: MediaTttFlags,
+ private val uiEventLogger: MediaTttSenderUiEventLogger,
+) : CoreStartable {
+
+ private var displayedState: ChipStateSender? = null
+
+ private val commandQueueCallbacks =
+ object : CommandQueue.Callbacks {
+ override fun updateMediaTapToTransferSenderDisplay(
+ @StatusBarManager.MediaTransferSenderState displayState: Int,
+ routeInfo: MediaRoute2Info,
+ undoCallback: IUndoMediaTransferCallback?
+ ) {
+ this@MediaTttSenderCoordinator.updateMediaTapToTransferSenderDisplay(
+ displayState,
+ routeInfo,
+ undoCallback
+ )
+ }
+ }
+
+ override fun start() {
+ if (mediaTttFlags.isMediaTttEnabled()) {
+ commandQueue.addCallback(commandQueueCallbacks)
+ }
+ }
+
+ private fun updateMediaTapToTransferSenderDisplay(
+ @StatusBarManager.MediaTransferSenderState displayState: Int,
+ routeInfo: MediaRoute2Info,
+ undoCallback: IUndoMediaTransferCallback?
+ ) {
+ val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState)
+ val stateName = chipState?.name ?: "Invalid"
+ logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
+
+ if (chipState == null) {
+ Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState")
+ return
+ }
+ uiEventLogger.logSenderStateChange(chipState)
+
+ if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
+ // Return early if we're not displaying a chip anyway
+ val currentDisplayedState = displayedState ?: return
+
+ val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name
+ if (
+ currentDisplayedState.transferStatus == TransferStatus.IN_PROGRESS ||
+ currentDisplayedState.transferStatus == TransferStatus.SUCCEEDED
+ ) {
+ // Don't remove the chip if we're in progress or succeeded, since the user should
+ // still be able to see the status of the transfer.
+ logger.logRemovalBypass(
+ removalReason,
+ bypassReason = "transferStatus=${currentDisplayedState.transferStatus.name}"
+ )
+ return
+ }
+
+ displayedState = null
+ chipbarCoordinator.removeView(removalReason)
+ } else {
+ displayedState = chipState
+ chipbarCoordinator.displayView(
+ createChipbarInfo(
+ chipState,
+ routeInfo,
+ undoCallback,
+ context,
+ logger,
+ )
+ )
+ }
+ }
+
+ /**
+ * Creates an instance of [ChipbarInfo] that can be sent to [ChipbarCoordinator] for display.
+ */
+ private fun createChipbarInfo(
+ chipStateSender: ChipStateSender,
+ routeInfo: MediaRoute2Info,
+ undoCallback: IUndoMediaTransferCallback?,
+ context: Context,
+ logger: MediaTttLogger,
+ ): ChipbarInfo {
+ val packageName = routeInfo.clientPackageName
+ val otherDeviceName = routeInfo.name.toString()
+
+ return ChipbarInfo(
+ // Display the app's icon as the start icon
+ startIcon = MediaTttUtils.getIconFromPackageName(context, packageName, logger),
+ text = chipStateSender.getChipTextString(context, otherDeviceName),
+ endItem =
+ when (chipStateSender.endItem) {
+ null -> null
+ is SenderEndItem.Loading -> ChipbarEndItem.Loading
+ is SenderEndItem.Error -> ChipbarEndItem.Error
+ is SenderEndItem.UndoButton -> {
+ if (undoCallback != null) {
+ getUndoButton(
+ undoCallback,
+ chipStateSender.endItem.uiEventOnClick,
+ chipStateSender.endItem.newState,
+ routeInfo,
+ )
+ } else {
+ null
+ }
+ }
+ },
+ vibrationEffect = chipStateSender.transferStatus.vibrationEffect,
+ )
+ }
+
+ /**
+ * Returns an undo button for the chip.
+ *
+ * When the button is clicked: [undoCallback] will be triggered, [uiEvent] will be logged, and
+ * this coordinator will transition to [newState].
+ */
+ private fun getUndoButton(
+ undoCallback: IUndoMediaTransferCallback,
+ uiEvent: UiEventLogger.UiEventEnum,
+ @StatusBarManager.MediaTransferSenderState newState: Int,
+ routeInfo: MediaRoute2Info,
+ ): ChipbarEndItem.Button {
+ val onClickListener =
+ View.OnClickListener {
+ uiEventLogger.logUndoClicked(uiEvent)
+ undoCallback.onUndoTriggered()
+
+ // The external service should eventually send us a new TransferTriggered state, but
+ // but that may take too long to go through the binder and the user may be confused
+ // as to why the UI hasn't changed yet. So, we immediately change the UI here.
+ updateMediaTapToTransferSenderDisplay(
+ newState,
+ routeInfo,
+ // Since we're force-updating the UI, we don't have any [undoCallback] from the
+ // external service (and TransferTriggered states don't have undo callbacks
+ // anyway).
+ undoCallback = null,
+ )
+ }
+
+ return ChipbarEndItem.Button(
+ Text.Resource(R.string.media_transfer_undo),
+ onClickListener,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt
index f15720df5245..b96380976dec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt
@@ -16,16 +16,36 @@
package com.android.systemui.media.taptotransfer.sender
-/** Represents the different possible transfer states that we could be in. */
-enum class TransferStatus {
+import android.os.VibrationEffect
+
+/**
+ * Represents the different possible transfer states that we could be in and the vibration effects
+ * that come with updating transfer states.
+ *
+ * @property vibrationEffect an optional vibration effect when the transfer status is changed.
+ */
+enum class TransferStatus(
+ val vibrationEffect: VibrationEffect? = null,
+) {
/** The transfer hasn't started yet. */
- NOT_STARTED,
+ NOT_STARTED(
+ vibrationEffect =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1.0f, 0)
+ .compose()
+ ),
/** The transfer is currently ongoing but hasn't completed yet. */
- IN_PROGRESS,
+ IN_PROGRESS(
+ vibrationEffect =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 1.0f, 0)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.7f, 70)
+ .compose(),
+ ),
/** The transfer has completed successfully. */
SUCCEEDED,
/** The transfer has completed with a failure. */
- FAILED,
+ FAILED(vibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)),
/** The device is too far away to do a transfer. */
TOO_FAR,
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 6da8c69c013b..c089511a7ce9 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -679,7 +679,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
public void onViewAttached() {
final Display display = mView.getDisplay();
mView.setComponents(mRecentsOptional);
- mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+ if (mCentralSurfacesOptionalLazy.get().isPresent()) {
+ mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+ }
mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
mView.setOnVerticalChangedListener(this::onVerticalChanged);
mView.setOnTouchListener(this::onNavigationTouch);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 97024881ca62..403d276f8cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -148,6 +148,7 @@ public class NavigationBarView extends FrameLayout {
private NavigationBarInflaterView mNavigationInflaterView;
private Optional<Recents> mRecentsOptional = Optional.empty();
+ @Nullable
private NotificationPanelViewController mPanelView;
private RotationContextButton mRotationContextButton;
private FloatingRotationButton mFloatingRotationButton;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 6424256f36ab..6e927b071727 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -417,6 +417,7 @@ class BackPanelController private constructor(
stretchEntryBackIndicator(preThresholdStretchProgress(xTranslation))
GestureState.INACTIVE ->
mView.resetStretch()
+ else -> {}
}
// set y translation
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
index 1ea93474f954..03503fd1ff61 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -17,10 +17,10 @@
package com.android.systemui.privacy.logging
import android.permission.PermissionGroupUsage
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.PrivacyLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogMessage
import com.android.systemui.privacy.PrivacyDialog
import com.android.systemui.privacy.PrivacyItem
import java.text.SimpleDateFormat
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 482a1397642b..bb2b4419a80a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -52,6 +52,7 @@ import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -98,10 +99,10 @@ interface FgsManagerController {
fun init()
/**
- * Show the foreground services dialog. The dialog will be expanded from [viewLaunchedFrom] if
+ * Show the foreground services dialog. The dialog will be expanded from [expandable] if
* it's not `null`.
*/
- fun showDialog(viewLaunchedFrom: View?)
+ fun showDialog(expandable: Expandable?)
/** Add a [OnNumberOfPackagesChangedListener]. */
fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener)
@@ -367,7 +368,7 @@ class FgsManagerControllerImpl @Inject constructor(
override fun shouldUpdateFooterVisibility() = dialog == null
- override fun showDialog(viewLaunchedFrom: View?) {
+ override fun showDialog(expandable: Expandable?) {
synchronized(lock) {
if (dialog == null) {
@@ -403,16 +404,18 @@ class FgsManagerControllerImpl @Inject constructor(
}
mainExecutor.execute {
- viewLaunchedFrom
- ?.let {
- dialogLaunchAnimator.showFromView(
- dialog, it,
- cuj = DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG
- )
+ val controller =
+ expandable?.dialogLaunchController(
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG,
)
- } ?: dialog.show()
+ )
+ if (controller != null) {
+ dialogLaunchAnimator.show(dialog, controller)
+ } else {
+ dialog.show()
+ }
}
backgroundExecutor.execute {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 9d64781ef2e9..a9943e886339 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -32,6 +32,7 @@ import com.android.internal.logging.nano.MetricsProto
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -156,7 +157,7 @@ internal class FooterActionsController @Inject constructor(
startSettingsActivity()
} else if (v === powerMenuLite) {
uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
- globalActionsDialog?.showOrHideDialog(false, true, v)
+ globalActionsDialog?.showOrHideDialog(false, true, Expandable.fromView(powerMenuLite))
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
index 7511278e0919..b1b9dd721eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
@@ -29,6 +29,7 @@ import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.systemui.R;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.dagger.QSScope;
@@ -130,7 +131,7 @@ public class QSFgsManagerFooter implements View.OnClickListener,
@Override
public void onClick(View view) {
- mFgsManagerController.showDialog(mRootView);
+ mFgsManagerController.showDialog(Expandable.fromView(view));
}
public void refreshState() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 0fe3d1699de0..20f1a8ed7689 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -49,7 +49,7 @@ import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
index 8544f61d7031..c663aa605ca2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.qs
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.QSFragmentDisableLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.DisableFlagsLogger
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f6db775a7749..abc0adecbfeb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -29,9 +29,9 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaHierarchyManager;
-import com.android.systemui.media.MediaHost;
-import com.android.systemui.media.MediaHostState;
+import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.MediaHostState;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 2727c83ad877..2a80de0e24de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -32,7 +32,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.customize.QSCustomizerController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 67bf3003deff..6c1e95645550 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -39,6 +39,7 @@ import androidx.annotation.Nullable;
import com.android.internal.util.FrameworkStatsLog;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.common.shared.model.Icon;
import com.android.systemui.dagger.qualifiers.Background;
@@ -169,7 +170,7 @@ public class QSSecurityFooter extends ViewController<View>
// TODO(b/242040009): Remove this.
public void showDeviceMonitoringDialog() {
- mQSSecurityFooterUtils.showDeviceMonitoringDialog(mContext, mView);
+ mQSSecurityFooterUtils.showDeviceMonitoringDialog(mContext, Expandable.fromView(mView));
}
public void refreshState() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index ae6ed2008a77..67bc76998597 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -75,6 +75,7 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.R;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.common.shared.model.ContentDescription;
import com.android.systemui.common.shared.model.Icon;
import com.android.systemui.dagger.SysUISingleton;
@@ -190,8 +191,9 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
}
/** Show the device monitoring dialog. */
- public void showDeviceMonitoringDialog(Context quickSettingsContext, @Nullable View view) {
- createDialog(quickSettingsContext, view);
+ public void showDeviceMonitoringDialog(Context quickSettingsContext,
+ @Nullable Expandable expandable) {
+ createDialog(quickSettingsContext, expandable);
}
/**
@@ -440,7 +442,7 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
}
}
- private void createDialog(Context quickSettingsContext, @Nullable View view) {
+ private void createDialog(Context quickSettingsContext, @Nullable Expandable expandable) {
mShouldUseSettingsButton.set(false);
mBgHandler.post(() -> {
String settingsButtonText = getSettingsButton();
@@ -453,9 +455,12 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
? settingsButtonText : getNegativeButton(), this);
mDialog.setView(dialogView);
- if (view != null && view.isAggregatedVisible()) {
- mDialogLaunchAnimator.showFromView(mDialog, view, new DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG));
+ DialogLaunchAnimator.Controller controller =
+ expandable != null ? expandable.dialogLaunchController(new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG))
+ : null;
+ if (controller != null) {
+ mDialogLaunchAnimator.show(mDialog, controller);
} else {
mDialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index ac46c85c10a4..f37d66877069 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -34,10 +34,12 @@ import com.android.internal.logging.InstanceId;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
+import com.android.systemui.ProtoDumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.nano.SystemUIProtoDump;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
@@ -48,6 +50,7 @@ import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServiceKey;
import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.nano.QsTileState;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
@@ -59,16 +62,20 @@ import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.leak.GarbageMonitor;
import com.android.systemui.util.settings.SecureSettings;
+import org.jetbrains.annotations.NotNull;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -82,7 +89,7 @@ import javax.inject.Provider;
* This class also provides the interface for adding/removing/changing tiles.
*/
@SysUISingleton
-public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, Dumpable {
+public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable {
private static final String TAG = "QSTileHost";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int MAX_QS_INSTANCE_ID = 1 << 20;
@@ -671,4 +678,15 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
mTiles.values().stream().filter(obj -> obj instanceof Dumpable)
.forEach(o -> ((Dumpable) o).dump(pw, args));
}
+
+ @Override
+ public void dumpProto(@NotNull SystemUIProtoDump systemUIProtoDump, @NotNull String[] args) {
+ List<QsTileState> data = mTiles.values().stream()
+ .map(QSTile::getState)
+ .map(TileStateToProtoKt::toProto)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+
+ systemUIProtoDump.tiles = data.toArray(new QsTileState[0]);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 9739974256f6..6aabe3b1ced1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -26,8 +26,8 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaHierarchyManager;
-import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 84d7e65d289b..27d9da6c2e1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -25,6 +25,7 @@ import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.DisplayCutout;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -231,6 +232,16 @@ public class QuickStatusBarHeader extends FrameLayout {
}
}
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // If using combined headers, only react to touches inside QuickQSPanel
+ if (!mUseCombinedQSHeader || event.getY() > mHeaderQsPanel.getTop()) {
+ return super.onTouchEvent(event);
+ } else {
+ return false;
+ }
+ }
+
void updateResources() {
Resources resources = mContext.getResources();
boolean largeScreenHeaderActive =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
new file mode 100644
index 000000000000..2c8a5a4981d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.external.CustomTile
+import com.android.systemui.qs.nano.QsTileState
+import com.android.systemui.util.nano.ComponentNameProto
+
+fun QSTile.State.toProto(): QsTileState? {
+ if (TextUtils.isEmpty(spec)) return null
+ val state = QsTileState()
+ if (spec.startsWith(CustomTile.PREFIX)) {
+ val protoComponentName = ComponentNameProto()
+ val tileComponentName = CustomTile.getComponentFromSpec(spec)
+ protoComponentName.packageName = tileComponentName.packageName
+ protoComponentName.className = tileComponentName.className
+ state.componentName = protoComponentName
+ } else {
+ state.spec = spec
+ }
+ state.state =
+ when (this.state) {
+ Tile.STATE_UNAVAILABLE -> QsTileState.UNAVAILABLE
+ Tile.STATE_INACTIVE -> QsTileState.INACTIVE
+ Tile.STATE_ACTIVE -> QsTileState.ACTIVE
+ else -> QsTileState.UNAVAILABLE
+ }
+ label?.let { state.label = it.toString() }
+ secondaryLabel?.let { state.secondaryLabel = it.toString() }
+ if (this is QSTile.BooleanState) {
+ state.booleanState = value
+ }
+ return state
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 4cacbbacec2f..5d03da3cc113 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.settings.UserTracker;
@@ -53,6 +54,7 @@ import javax.inject.Provider;
/**
* Runs the day-to-day operations of which tiles should be bound and when.
*/
+@SysUISingleton
public class TileServices extends IQSService.Stub {
static final int DEFAULT_MAX_BOUND = 3;
static final int REDUCED_MAX_BOUND = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index cf9b41c25388..9ba3501c3434 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -23,13 +23,11 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
import android.provider.Settings
-import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.internal.logging.nano.MetricsProto
import com.android.internal.util.FrameworkStatsLog
-import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
@@ -74,37 +72,27 @@ interface FooterActionsInteractor {
val deviceMonitoringDialogRequests: Flow<Unit>
/**
- * Show the device monitoring dialog, expanded from [view].
- *
- * Important: [view] must be associated to the same [Context] as the [Quick Settings fragment]
- * [com.android.systemui.qs.QSFragment].
- */
- // TODO(b/230830644): Replace view by Expandable interface.
- fun showDeviceMonitoringDialog(view: View)
-
- /**
- * Show the device monitoring dialog.
+ * Show the device monitoring dialog, expanded from [expandable] if it's not null.
*
* Important: [quickSettingsContext] *must* be the [Context] associated to the [Quick Settings
* fragment][com.android.systemui.qs.QSFragment].
*/
- // TODO(b/230830644): Replace view by Expandable interface.
- fun showDeviceMonitoringDialog(quickSettingsContext: Context)
+ fun showDeviceMonitoringDialog(quickSettingsContext: Context, expandable: Expandable?)
/** Show the foreground services dialog. */
- // TODO(b/230830644): Replace view by Expandable interface.
- fun showForegroundServicesDialog(view: View)
+ fun showForegroundServicesDialog(expandable: Expandable)
/** Show the power menu dialog. */
- // TODO(b/230830644): Replace view by Expandable interface.
- fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View)
+ fun showPowerMenuDialog(
+ globalActionsDialogLite: GlobalActionsDialogLite,
+ expandable: Expandable,
+ )
/** Show the settings. */
fun showSettings(expandable: Expandable)
/** Show the user switcher. */
- // TODO(b/230830644): Replace view by Expandable interface.
- fun showUserSwitcher(view: View)
+ fun showUserSwitcher(context: Context, expandable: Expandable)
}
@SysUISingleton
@@ -147,28 +135,32 @@ constructor(
null,
)
- override fun showDeviceMonitoringDialog(view: View) {
- qsSecurityFooterUtils.showDeviceMonitoringDialog(view.context, view)
- DevicePolicyEventLogger.createEvent(
- FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED
- )
- .write()
- }
-
- override fun showDeviceMonitoringDialog(quickSettingsContext: Context) {
- qsSecurityFooterUtils.showDeviceMonitoringDialog(quickSettingsContext, /* view= */ null)
+ override fun showDeviceMonitoringDialog(
+ quickSettingsContext: Context,
+ expandable: Expandable?,
+ ) {
+ qsSecurityFooterUtils.showDeviceMonitoringDialog(quickSettingsContext, expandable)
+ if (expandable != null) {
+ DevicePolicyEventLogger.createEvent(
+ FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED
+ )
+ .write()
+ }
}
- override fun showForegroundServicesDialog(view: View) {
- fgsManagerController.showDialog(view)
+ override fun showForegroundServicesDialog(expandable: Expandable) {
+ fgsManagerController.showDialog(expandable)
}
- override fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View) {
+ override fun showPowerMenuDialog(
+ globalActionsDialogLite: GlobalActionsDialogLite,
+ expandable: Expandable,
+ ) {
uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
globalActionsDialogLite.showOrHideDialog(
/* keyguardShowing= */ false,
/* isDeviceProvisioned= */ true,
- view,
+ expandable,
)
}
@@ -189,21 +181,21 @@ constructor(
)
}
- override fun showUserSwitcher(view: View) {
+ override fun showUserSwitcher(context: Context, expandable: Expandable) {
if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- userSwitchDialogController.showDialog(view)
+ userSwitchDialogController.showDialog(context, expandable)
return
}
val intent =
- Intent(view.context, UserSwitcherActivity::class.java).apply {
+ Intent(context, UserSwitcherActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
}
activityStarter.startActivity(
intent,
true /* dismissShade */,
- ActivityLaunchAnimator.Controller.fromView(view, null),
+ expandable.activityLaunchController(),
true /* showOverlockscreenwhenlocked */,
UserHandle.SYSTEM,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index dd1ffcc9fa12..3e39c8ee62f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -31,6 +31,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.people.ui.view.PeopleViewBinder.bind
@@ -125,7 +126,7 @@ object FooterActionsViewBinder {
launch {
viewModel.security.collect { security ->
if (previousSecurity != security) {
- bindSecurity(securityHolder, security)
+ bindSecurity(view.context, securityHolder, security)
previousSecurity = security
}
}
@@ -159,6 +160,7 @@ object FooterActionsViewBinder {
}
private fun bindSecurity(
+ quickSettingsContext: Context,
securityHolder: TextButtonViewHolder,
security: FooterActionsSecurityButtonViewModel?,
) {
@@ -171,9 +173,12 @@ object FooterActionsViewBinder {
// Make sure that the chevron is visible and that the button is clickable if there is a
// listener.
val chevron = securityHolder.chevron
- if (security.onClick != null) {
+ val onClick = security.onClick
+ if (onClick != null) {
securityView.isClickable = true
- securityView.setOnClickListener(security.onClick)
+ securityView.setOnClickListener {
+ onClick(quickSettingsContext, Expandable.fromView(securityView))
+ }
chevron.isVisible = true
} else {
securityView.isClickable = false
@@ -205,7 +210,9 @@ object FooterActionsViewBinder {
foregroundServicesWithNumberView.isVisible = false
foregroundServicesWithTextView.isVisible = true
- foregroundServicesWithTextView.setOnClickListener(foregroundServices.onClick)
+ foregroundServicesWithTextView.setOnClickListener {
+ foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView))
+ }
foregroundServicesWithTextHolder.text.text = foregroundServices.text
foregroundServicesWithTextHolder.newDot.isVisible = foregroundServices.hasNewChanges
} else {
@@ -213,7 +220,9 @@ object FooterActionsViewBinder {
foregroundServicesWithTextView.isVisible = false
foregroundServicesWithNumberView.visibility = View.VISIBLE
- foregroundServicesWithNumberView.setOnClickListener(foregroundServices.onClick)
+ foregroundServicesWithNumberView.setOnClickListener {
+ foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView))
+ }
foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString()
foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text
foregroundServicesWithNumberHolder.newDot.isVisible = foregroundServices.hasNewChanges
@@ -229,7 +238,7 @@ object FooterActionsViewBinder {
}
buttonView.setBackgroundResource(model.background)
- buttonView.setOnClickListener(model.onClick)
+ buttonView.setOnClickListener { model.onClick(Expandable.fromView(buttonView)) }
val icon = model.icon
val iconView = button.icon
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
index 9b5f683d8dab..8d819dacba67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.qs.footer.ui.viewmodel
import android.annotation.DrawableRes
-import android.view.View
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
/**
@@ -29,7 +29,5 @@ data class FooterActionsButtonViewModel(
val icon: Icon,
val iconTint: Int?,
@DrawableRes val background: Int,
- // TODO(b/230830644): Replace View by an Expandable interface that can expand in either dialog
- // or activity.
- val onClick: (View) -> Unit,
+ val onClick: (Expandable) -> Unit,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt
index 98b53cb0ed5a..ff8130d3e6ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.qs.footer.ui.viewmodel
-import android.view.View
+import com.android.systemui.animation.Expandable
/** A ViewModel for the foreground services button. */
data class FooterActionsForegroundServicesButtonViewModel(
@@ -24,5 +24,5 @@ data class FooterActionsForegroundServicesButtonViewModel(
val text: String,
val displayText: Boolean,
val hasNewChanges: Boolean,
- val onClick: (View) -> Unit,
+ val onClick: (Expandable) -> Unit,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt
index 98ab129fc9de..3450505f9f86 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt
@@ -16,12 +16,13 @@
package com.android.systemui.qs.footer.ui.viewmodel
-import android.view.View
+import android.content.Context
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
/** A ViewModel for the security button. */
data class FooterActionsSecurityButtonViewModel(
val icon: Icon,
val text: String,
- val onClick: ((View) -> Unit)?,
+ val onClick: ((quickSettingsContext: Context, Expandable) -> Unit)?,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index d3c06f60bc90..dee6fadbc9cb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -18,7 +18,6 @@ package com.android.systemui.qs.footer.ui.viewmodel
import android.content.Context
import android.util.Log
-import android.view.View
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
@@ -199,50 +198,51 @@ class FooterActionsViewModel(
*/
suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
footerActionsInteractor.deviceMonitoringDialogRequests.collect {
- footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext)
+ footerActionsInteractor.showDeviceMonitoringDialog(
+ quickSettingsContext,
+ expandable = null,
+ )
}
}
- private fun onSecurityButtonClicked(view: View) {
+ private fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
- footerActionsInteractor.showDeviceMonitoringDialog(view)
+ footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext, expandable)
}
- private fun onForegroundServiceButtonClicked(view: View) {
+ private fun onForegroundServiceButtonClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
- footerActionsInteractor.showForegroundServicesDialog(view)
+ footerActionsInteractor.showForegroundServicesDialog(expandable)
}
- private fun onUserSwitcherClicked(view: View) {
+ private fun onUserSwitcherClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
- footerActionsInteractor.showUserSwitcher(view)
+ footerActionsInteractor.showUserSwitcher(context, expandable)
}
- // TODO(b/230830644): Replace View by an Expandable interface that can expand in either dialog
- // or activity.
- private fun onSettingsButtonClicked(view: View) {
+ private fun onSettingsButtonClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
- footerActionsInteractor.showSettings(Expandable.fromView(view))
+ footerActionsInteractor.showSettings(expandable)
}
- private fun onPowerButtonClicked(view: View) {
+ private fun onPowerButtonClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
- footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, view)
+ footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, expandable)
}
private fun userSwitcherButton(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 60380064e098..931dc8df151a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -17,12 +17,12 @@
package com.android.systemui.qs.logging
import android.service.quicksettings.Tile
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.VERBOSE
-import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.QSLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.plugins.log.LogMessage
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.StatusBarState
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto b/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto
new file mode 100644
index 000000000000..2a61033cb302
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package com.android.systemui.qs;
+
+import "frameworks/base/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto";
+
+option java_multiple_files = true;
+
+message QsTileState {
+ oneof identifier {
+ string spec = 1;
+ com.android.systemui.util.ComponentNameProto component_name = 2;
+ }
+
+ enum State {
+ UNAVAILABLE = 0;
+ INACTIVE = 1;
+ ACTIVE = 2;
+ }
+
+ State state = 3;
+ oneof optional_boolean_state {
+ bool boolean_state = 4;
+ }
+ oneof optional_label {
+ string label = 5;
+ }
+ oneof optional_secondary_label {
+ string secondary_label = 6;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index d2d5063c7ae0..b6b657ec82f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -26,6 +26,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
@@ -43,6 +44,9 @@ import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.user.data.source.UserRecord;
+import java.util.List;
+import java.util.stream.Collectors;
+
import javax.inject.Inject;
/**
@@ -83,6 +87,13 @@ public class UserDetailView extends PseudoGridView {
private final FalsingManager mFalsingManager;
private @Nullable UserSwitchDialogController.DialogShower mDialogShower;
+ @NonNull
+ @Override
+ protected List<UserRecord> getUsers() {
+ return super.getUsers().stream().filter(
+ userRecord -> !userRecord.isManageUsers).collect(Collectors.toList());
+ }
+
@Inject
public Adapter(Context context, UserSwitcherController controller,
UiEventLogger uiEventLogger, FalsingManager falsingManager) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index bdcc6b0b2a57..314252bf310b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -23,13 +23,13 @@ import android.content.DialogInterface.BUTTON_NEUTRAL
import android.content.Intent
import android.provider.Settings
import android.view.LayoutInflater
-import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -77,10 +77,10 @@ class UserSwitchDialogController @VisibleForTesting constructor(
* Show a [UserDialog].
*
* Populate the dialog with information from and adapter obtained from
- * [userDetailViewAdapterProvider] and show it as launched from [view].
+ * [userDetailViewAdapterProvider] and show it as launched from [expandable].
*/
- fun showDialog(view: View) {
- with(dialogFactory(view.context)) {
+ fun showDialog(context: Context, expandable: Expandable) {
+ with(dialogFactory(context)) {
setShowForAllUsers(true)
setCanceledOnTouchOutside(true)
@@ -112,13 +112,19 @@ class UserSwitchDialogController @VisibleForTesting constructor(
adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
- dialogLaunchAnimator.showFromView(
- this, view,
- cuj = DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG
+ val controller =
+ expandable.dialogLaunchController(
+ DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
)
- )
+ if (controller != null) {
+ dialogLaunchAnimator.show(
+ this,
+ controller,
+ )
+ } else {
+ show()
+ }
+
uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
new file mode 100644
index 000000000000..017e57fcaf62
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import com.android.systemui.R
+
+object ActionIntentCreator {
+ /** @return a chooser intent to share the given URI with the optional provided subject. */
+ fun createShareIntent(uri: Uri, subject: String?): Intent {
+ // Create a share intent, this will always go through the chooser activity first
+ // which should not trigger auto-enter PiP
+ val sharingIntent =
+ Intent(Intent.ACTION_SEND).apply {
+ setDataAndType(uri, "image/png")
+ putExtra(Intent.EXTRA_STREAM, uri)
+
+ // Include URI in ClipData also, so that grantPermission picks it up.
+ // We don't use setData here because some apps interpret this as "to:".
+ clipData =
+ ClipData(
+ ClipDescription("content", arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)),
+ ClipData.Item(uri)
+ )
+
+ putExtra(Intent.EXTRA_SUBJECT, subject)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+ }
+
+ return Intent.createChooser(sharingIntent, null)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+
+ /**
+ * @return an ACTION_EDIT intent for the given URI, directed to config_screenshotEditor if
+ * available.
+ */
+ fun createEditIntent(uri: Uri, context: Context): Intent {
+ val editIntent = Intent(Intent.ACTION_EDIT)
+
+ context.getString(R.string.config_screenshotEditor)?.let {
+ if (it.isNotEmpty()) {
+ editIntent.component = ComponentName.unflattenFromString(it)
+ }
+ }
+
+ return editIntent
+ .setDataAndType(uri, "image/png")
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
new file mode 100644
index 000000000000..5961635a0dba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.RemoteException
+import android.os.UserHandle
+import android.util.Log
+import android.view.Display
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
+import android.view.RemoteAnimationAdapter
+import android.view.RemoteAnimationTarget
+import android.view.WindowManager
+import android.view.WindowManagerGlobal
+import com.android.internal.infra.ServiceConnector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class ActionIntentExecutor
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val context: Context,
+) {
+ /**
+ * Execute the given intent with startActivity while performing operations for screenshot action
+ * launching.
+ * - Dismiss the keyguard first
+ * - If the userId is not the current user, proxy to a service running as that user to execute
+ * - After startActivity, optionally override the pending app transition.
+ */
+ fun launchIntentAsync(
+ intent: Intent,
+ bundle: Bundle,
+ userId: Int,
+ overrideTransition: Boolean,
+ ) {
+ applicationScope.launch { launchIntent(intent, bundle, userId, overrideTransition) }
+ }
+
+ suspend fun launchIntent(
+ intent: Intent,
+ bundle: Bundle,
+ userId: Int,
+ overrideTransition: Boolean,
+ ) {
+ withContext(bgDispatcher) {
+ dismissKeyguard()
+
+ if (userId == UserHandle.myUserId()) {
+ context.startActivity(intent, bundle)
+ } else {
+ launchCrossProfileIntent(userId, intent, bundle)
+ }
+
+ if (overrideTransition) {
+ val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY)
+ } catch (e: Exception) {
+ Log.e(TAG, "Error overriding screenshot app transition", e)
+ }
+ }
+ }
+ }
+
+ private val proxyConnector: ServiceConnector<IScreenshotProxy> =
+ ServiceConnector.Impl(
+ context,
+ Intent(context, ScreenshotProxyService::class.java),
+ Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+ context.userId,
+ IScreenshotProxy.Stub::asInterface,
+ )
+
+ private suspend fun dismissKeyguard() {
+ val completion = CompletableDeferred<Unit>()
+ val onDoneBinder =
+ object : IOnDoneCallback.Stub() {
+ override fun onDone(success: Boolean) {
+ completion.complete(Unit)
+ }
+ }
+ proxyConnector.post { it.dismissKeyguard(onDoneBinder) }
+ completion.await()
+ }
+
+ private fun getCrossProfileConnector(userId: Int): ServiceConnector<ICrossProfileService> =
+ ServiceConnector.Impl<ICrossProfileService>(
+ context,
+ Intent(context, ScreenshotCrossProfileService::class.java),
+ Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+ userId,
+ ICrossProfileService.Stub::asInterface,
+ )
+
+ private suspend fun launchCrossProfileIntent(userId: Int, intent: Intent, bundle: Bundle) {
+ val connector = getCrossProfileConnector(userId)
+ val completion = CompletableDeferred<Unit>()
+ connector.post {
+ it.launchIntent(intent, bundle)
+ completion.complete(Unit)
+ }
+ completion.await()
+ }
+}
+
+private const val TAG: String = "ActionIntentExecutor"
+private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
+
+/**
+ * This is effectively a no-op, but we need something non-null to pass in, in order to successfully
+ * override the pending activity entrance animation.
+ */
+private val SCREENSHOT_REMOTE_RUNNER: IRemoteAnimationRunner.Stub =
+ object : IRemoteAnimationRunner.Stub() {
+ override fun onAnimationStart(
+ @WindowManager.TransitionOldType transit: Int,
+ apps: Array<RemoteAnimationTarget>,
+ wallpapers: Array<RemoteAnimationTarget>,
+ nonApps: Array<RemoteAnimationTarget>,
+ finishedCallback: IRemoteAnimationFinishedCallback,
+ ) {
+ try {
+ finishedCallback.onAnimationFinished()
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Error finishing screenshot remote animation", e)
+ }
+ }
+
+ override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {}
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl
new file mode 100644
index 000000000000..da834729d319
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2009, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+
+/** Interface implemented by ScreenshotCrossProfileService */
+interface ICrossProfileService {
+
+ void launchIntent(in Intent intent, in Bundle bundle);
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl
new file mode 100644
index 000000000000..e15030f78234
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+interface IOnDoneCallback {
+ void onDone(boolean success);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
index f7c4dadc6605..d2e3fbd65762 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
@@ -16,9 +16,14 @@
package com.android.systemui.screenshot;
+import com.android.systemui.screenshot.IOnDoneCallback;
+
/** Interface implemented by ScreenshotProxyService */
interface IScreenshotProxy {
/** Is the notification shade currently exanded? */
boolean isNotificationShadeExpanded();
-} \ No newline at end of file
+
+ /** Attempts to dismiss the keyguard. */
+ void dismissKeyguard(IOnDoneCallback callback);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 077ad35fd63f..7143ba263570 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -173,6 +173,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
mImageData.quickShareAction = createQuickShareAction(mContext,
mQuickShareData.quickShareAction, uri);
+ mImageData.subject = getSubjectString();
mParams.mActionsReadyListener.onActionsReady(mImageData);
if (DEBUG_CALLBACK) {
@@ -237,8 +238,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// Create a share intent, this will always go through the chooser activity first
// which should not trigger auto-enter PiP
- String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
- String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setDataAndType(uri, "image/png");
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
@@ -248,7 +247,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
new ClipData.Item(uri));
sharingIntent.setClipData(clipdata);
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString());
sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
@@ -318,7 +317,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// by setting the (otherwise unused) request code to the current user id.
int requestCode = mContext.getUserId();
- // Create a edit action
+ // Create an edit action
PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
new Intent(context, ActionProxyReceiver.class)
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
@@ -479,4 +478,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
}
}
+
+ private String getSubjectString() {
+ String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
+ return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6d5121a89241..231e415f17c6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -174,7 +174,7 @@ public class ScreenshotController {
public List<Notification.Action> smartActions;
public Notification.Action quickShareAction;
public UserHandle owner;
-
+ public String subject; // Title for sharing
/**
* POD for shared element transition.
@@ -195,6 +195,7 @@ public class ScreenshotController {
deleteAction = null;
smartActions = null;
quickShareAction = null;
+ subject = null;
}
}
@@ -273,6 +274,7 @@ public class ScreenshotController {
private final ScreenshotNotificationSmartActionsProvider
mScreenshotNotificationSmartActionsProvider;
private final TimeoutHandler mScreenshotHandler;
+ private final ActionIntentExecutor mActionExecutor;
private ScreenshotView mScreenshotView;
private Bitmap mScreenBitmap;
@@ -310,7 +312,8 @@ public class ScreenshotController {
ActivityManager activityManager,
TimeoutHandler timeoutHandler,
BroadcastSender broadcastSender,
- ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider
+ ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
+ ActionIntentExecutor actionExecutor
) {
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
@@ -340,6 +343,7 @@ public class ScreenshotController {
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mWindowManager = mContext.getSystemService(WindowManager.class);
mFlags = flags;
+ mActionExecutor = actionExecutor;
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -486,7 +490,7 @@ public class ScreenshotController {
// TODO(159460485): Remove this when focus is handled properly in the system
setWindowFocusable(false);
}
- });
+ }, mActionExecutor, mFlags);
mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt
new file mode 100644
index 000000000000..2e6c7567259f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.Service
+import android.content.Intent
+import android.os.Bundle
+import android.os.IBinder
+import android.util.Log
+
+/**
+ * If a screenshot is saved to the work profile, any intents that grant access to the screenshot
+ * must come from a service running as the work profile user. This service is meant to be started as
+ * the desired user and just startActivity for the given intent.
+ */
+class ScreenshotCrossProfileService : Service() {
+
+ private val mBinder: IBinder =
+ object : ICrossProfileService.Stub() {
+ override fun launchIntent(intent: Intent, bundle: Bundle) {
+ startActivity(intent, bundle)
+ }
+ }
+
+ override fun onBind(intent: Intent): IBinder? {
+ Log.d(TAG, "onBind: $intent")
+ return mBinder
+ }
+
+ companion object {
+ const val TAG = "ScreenshotProxyService"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 793085a60133..c41e2bc14afc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -20,13 +20,16 @@ import android.content.Intent
import android.os.IBinder
import android.util.Log
import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import java.util.Optional
import javax.inject.Inject
/**
* Provides state from the main SystemUI process on behalf of the Screenshot process.
*/
internal class ScreenshotProxyService @Inject constructor(
- private val mExpansionMgr: ShadeExpansionStateManager
+ private val mExpansionMgr: ShadeExpansionStateManager,
+ private val mCentralSurfacesOptional: Optional<CentralSurfaces>,
) : Service() {
private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
@@ -38,6 +41,20 @@ internal class ScreenshotProxyService @Inject constructor(
Log.d(TAG, "isNotificationShadeExpanded(): $expanded")
return expanded
}
+
+ override fun dismissKeyguard(callback: IOnDoneCallback) {
+ if (mCentralSurfacesOptional.isPresent) {
+ mCentralSurfacesOptional.get().executeRunnableDismissingKeyguard(
+ Runnable {
+ callback.onDone(true)
+ }, null,
+ true /* dismissShade */, true /* afterKeyguardGone */,
+ true /* deferred */
+ )
+ } else {
+ callback.onDone(false)
+ }
+ }
}
override fun onBind(intent: Intent): IBinder? {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index be41a6b0d376..1b9cdd42dbf7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -87,6 +87,8 @@ import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
@@ -168,6 +170,8 @@ public class ScreenshotView extends FrameLayout implements
private final InteractionJankMonitor mInteractionJankMonitor;
private long mDefaultTimeoutOfTimeoutHandler;
+ private ActionIntentExecutor mActionExecutor;
+ private FeatureFlags mFlags;
private enum PendingInteraction {
PREVIEW,
@@ -422,9 +426,12 @@ public class ScreenshotView extends FrameLayout implements
* Note: must be called before any other (non-constructor) method or null pointer exceptions
* may occur.
*/
- void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks) {
+ void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks,
+ ActionIntentExecutor actionExecutor, FeatureFlags flags) {
mUiEventLogger = uiEventLogger;
mCallbacks = callbacks;
+ mActionExecutor = actionExecutor;
+ mFlags = flags;
}
void setScreenshot(Bitmap bitmap, Insets screenInsets) {
@@ -759,18 +766,41 @@ public class ScreenshotView extends FrameLayout implements
void setChipIntents(ScreenshotController.SavedImageData imageData) {
mShareChip.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
- startSharedTransition(
- imageData.shareTransition.get());
+ if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+ prepareSharedTransition();
+ mActionExecutor.launchIntentAsync(
+ ActionIntentCreator.INSTANCE.createShareIntent(
+ imageData.uri, imageData.subject),
+ imageData.shareTransition.get().bundle,
+ imageData.owner.getIdentifier(), false);
+ } else {
+ startSharedTransition(imageData.shareTransition.get());
+ }
});
mEditChip.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
- startSharedTransition(
- imageData.editTransition.get());
+ if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+ prepareSharedTransition();
+ mActionExecutor.launchIntentAsync(
+ ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
+ imageData.editTransition.get().bundle,
+ imageData.owner.getIdentifier(), true);
+ } else {
+ startSharedTransition(imageData.editTransition.get());
+ }
});
mScreenshotPreview.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
- startSharedTransition(
- imageData.editTransition.get());
+ if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+ prepareSharedTransition();
+ mActionExecutor.launchIntentAsync(
+ ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
+ imageData.editTransition.get().bundle,
+ imageData.owner.getIdentifier(), true);
+ } else {
+ startSharedTransition(
+ imageData.editTransition.get());
+ }
});
if (mQuickShareChip != null) {
mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
@@ -1038,6 +1068,12 @@ public class ScreenshotView extends FrameLayout implements
}
}
+ private void prepareSharedTransition() {
+ mPendingSharedTransition = true;
+ // fade out non-preview UI
+ createScreenshotFadeDismissAnimation().start();
+ }
+
ValueAnimator createScreenshotFadeDismissAnimation() {
ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
alphaAnim.addUpdateListener(animation -> {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 6e9f859c202b..d5a395436271 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -20,6 +20,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.Activity;
+import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.view.Gravity;
@@ -36,6 +37,8 @@ import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
+import java.util.List;
+
import javax.inject.Inject;
/** A dialog that provides controls for adjusting the screen brightness. */
@@ -83,6 +86,15 @@ public class BrightnessDialog extends Activity {
lp.leftMargin = horizontalMargin;
lp.rightMargin = horizontalMargin;
frame.setLayoutParams(lp);
+ Rect bounds = new Rect();
+ frame.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ // Exclude this view (and its horizontal margins) from triggering gestures.
+ // This prevents back gesture from being triggered by dragging close to the
+ // edge of the slider (0% or 100%).
+ bounds.set(-horizontalMargin, 0, right - left + horizontalMargin, bottom - top);
+ v.setSystemGestureExclusionRects(List.of(bounds));
+ });
BrightnessSliderController controller = mToggleSliderFactory.create(this, frame);
controller.init();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index d3ed47407b9d..6b540aa9f392 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -280,6 +280,9 @@ class LargeScreenShadeHeaderController @Inject constructor(
context.getString(com.android.internal.R.string.status_bar_alarm_clock)
)
}
+ if (combinedHeaders) {
+ privacyIconsController.onParentVisible()
+ }
}
override fun onViewAttached() {
@@ -289,6 +292,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
v.pivotX = newPivot
+ v.pivotY = v.height.toFloat() / 2
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
index 07e8b9fe3123..754036d3baa9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
@@ -16,7 +16,7 @@ package com.android.systemui.shade
import android.view.MotionEvent
import com.android.systemui.dump.DumpsysTableLogger
import com.android.systemui.dump.Row
-import com.android.systemui.util.collection.RingBuffer
+import com.android.systemui.plugins.util.RingBuffer
import java.text.SimpleDateFormat
import java.util.Locale
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e3318127af93..ddb57f74cacf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -79,7 +79,10 @@ import android.os.UserManager;
import android.os.VibrationEffect;
import android.provider.Settings;
import android.transition.ChangeBounds;
+import android.transition.Transition;
import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.transition.TransitionValues;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
@@ -144,10 +147,12 @@ import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
-import com.android.systemui.media.KeyguardMediaController;
-import com.android.systemui.media.MediaDataManager;
-import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
@@ -580,6 +585,7 @@ public final class NotificationPanelViewController {
private final SysUiState mSysUiState;
private final NotificationShadeDepthController mDepthController;
+ private final NavigationBarController mNavigationBarController;
private final int mDisplayId;
private KeyguardIndicationController mKeyguardIndicationController;
@@ -689,6 +695,7 @@ public final class NotificationPanelViewController {
private int mScreenCornerRadius;
private boolean mQSAnimatingHiddenFromCollapsed;
private boolean mUseLargeScreenShadeHeader;
+ private boolean mEnableQsClipping;
private int mQsClipTop;
private int mQsClipBottom;
@@ -857,6 +864,7 @@ public final class NotificationPanelViewController {
PrivacyDotViewController privacyDotViewController,
TapAgainViewController tapAgainViewController,
NavigationModeController navigationModeController,
+ NavigationBarController navigationBarController,
FragmentService fragmentService,
ContentResolver contentResolver,
RecordingController recordingController,
@@ -950,6 +958,7 @@ public final class NotificationPanelViewController {
mNotificationsQSContainerController = notificationsQSContainerController;
mNotificationListContainer = notificationListContainer;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
+ mNavigationBarController = navigationBarController;
mKeyguardBottomAreaViewControllerProvider = keyguardBottomAreaViewControllerProvider;
mNotificationsQSContainerController.init();
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
@@ -1298,6 +1307,8 @@ public final class NotificationPanelViewController {
mSplitShadeFullTransitionDistance =
mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance);
+
+ mEnableQsClipping = mResources.getBoolean(R.bool.qs_enable_clipping);
}
private void onSplitShadeEnabledChanged() {
@@ -1437,6 +1448,16 @@ public final class NotificationPanelViewController {
mMaxAllowedKeyguardNotifications = maxAllowed;
}
+ @VisibleForTesting
+ boolean getClosing() {
+ return mClosing;
+ }
+
+ @VisibleForTesting
+ boolean getIsFlinging() {
+ return mIsFlinging;
+ }
+
private void updateMaxDisplayedNotifications(boolean recompute) {
if (recompute) {
setMaxDisplayedNotifications(Math.max(computeMaxKeyguardNotifications(), 1));
@@ -1664,9 +1685,40 @@ public final class NotificationPanelViewController {
// horizontally properly.
transition.excludeTarget(R.id.status_view_media_container, true);
}
+
transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- TransitionManager.beginDelayedTransition(mNotificationContainerParent, transition);
+
+ boolean customClockAnimation =
+ mKeyguardStatusViewController.getClockAnimations() != null
+ && mKeyguardStatusViewController.getClockAnimations()
+ .getHasCustomPositionUpdatedAnimation();
+
+ if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
+ // Find the clock, so we can exclude it from this transition.
+ FrameLayout clockContainerView =
+ mView.findViewById(R.id.lockscreen_clock_view_large);
+ View clockView = clockContainerView.getChildAt(0);
+
+ transition.excludeTarget(clockView, /* exclude= */ true);
+
+ TransitionSet set = new TransitionSet();
+ set.addTransition(transition);
+
+ SplitShadeTransitionAdapter adapter =
+ new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
+
+ // Use linear here, so the actual clock can pick its own interpolator.
+ adapter.setInterpolator(Interpolators.LINEAR);
+ adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ adapter.addTarget(clockView);
+ set.addTransition(adapter);
+
+ TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+ } else {
+ TransitionManager.beginDelayedTransition(
+ mNotificationContainerParent, transition);
+ }
}
constraintSet.applyTo(mNotificationContainerParent);
@@ -2090,7 +2142,8 @@ public final class NotificationPanelViewController {
animator.start();
}
- private void onFlingEnd(boolean cancelled) {
+ @VisibleForTesting
+ void onFlingEnd(boolean cancelled) {
mIsFlinging = false;
// No overshoot when the animation ends
setOverExpansionInternal(0, false /* isFromGesture */);
@@ -2164,7 +2217,8 @@ public final class NotificationPanelViewController {
mShadeLog.logMotionEvent(event,
"onQsIntercept: move ignored because qs tracking disabled");
}
- if ((h > getTouchSlop(event) || (h < -getTouchSlop(event) && mQsExpanded))
+ float touchSlop = getTouchSlop(event);
+ if ((h > touchSlop || (h < -touchSlop && mQsExpanded))
&& Math.abs(h) > Math.abs(x - mInitialTouchX)
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept - start tracking expansion");
@@ -2179,6 +2233,9 @@ public final class NotificationPanelViewController {
mInitialTouchX = x;
mNotificationStackScrollLayoutController.cancelLongPress();
return true;
+ } else {
+ mShadeLog.logQsTrackingNotStarted(mInitialTouchY, y, h, touchSlop, mQsExpanded,
+ mCollapsedOnDown, mKeyguardShowing, isQsExpansionEnabled());
}
break;
@@ -2629,12 +2686,16 @@ public final class NotificationPanelViewController {
mQsExpanded = expanded;
updateQsState();
updateExpandedHeightToMaxHeight();
- mFalsingCollector.setQsExpanded(expanded);
- mCentralSurfaces.setQsExpanded(expanded);
- mNotificationsQSContainerController.setQsExpanded(expanded);
- mPulseExpansionHandler.setQsExpanded(expanded);
- mKeyguardBypassController.setQSExpanded(expanded);
- mPrivacyDotViewController.setQsExpanded(expanded);
+ setStatusAccessibilityImportance(expanded
+ ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ updateSystemUiStateFlags();
+ NavigationBarView navigationBarView =
+ mNavigationBarController.getNavigationBarView(mDisplayId);
+ if (navigationBarView != null) {
+ navigationBarView.onStatusBarPanelStateChanged();
+ }
+ mShadeExpansionStateManager.onQsExpansionChanged(expanded);
}
}
@@ -2948,8 +3009,10 @@ public final class NotificationPanelViewController {
mQsTranslationForFullShadeTransition = qsTranslation;
updateQsFrameTranslation();
float currentTranslation = mQsFrame.getTranslationY();
- mQsClipTop = (int) (top - currentTranslation - mQsFrame.getTop());
- mQsClipBottom = (int) (bottom - currentTranslation - mQsFrame.getTop());
+ mQsClipTop = mEnableQsClipping
+ ? (int) (top - currentTranslation - mQsFrame.getTop()) : 0;
+ mQsClipBottom = mEnableQsClipping
+ ? (int) (bottom - currentTranslation - mQsFrame.getTop()) : 0;
mQsVisible = qsVisible;
mQs.setQsVisible(mQsVisible);
mQs.setFancyClipping(
@@ -3674,6 +3737,11 @@ public final class NotificationPanelViewController {
setListening(true);
}
+ @VisibleForTesting
+ void setTouchSlopExceeded(boolean isTouchSlopExceeded) {
+ mTouchSlopExceeded = isTouchSlopExceeded;
+ }
+
public void setOverExpansion(float overExpansion) {
if (overExpansion == mOverExpansion) {
return;
@@ -3825,12 +3893,14 @@ public final class NotificationPanelViewController {
}
}
- private void setIsClosing(boolean isClosing) {
+ @VisibleForTesting
+ void setIsClosing(boolean isClosing) {
boolean wasClosing = isClosing();
mClosing = isClosing;
if (wasClosing != isClosing) {
mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing);
}
+ mAmbientState.setIsClosing(isClosing);
}
private void updateDozingVisibilities(boolean animate) {
@@ -3860,12 +3930,16 @@ public final class NotificationPanelViewController {
switch (mBarState) {
case KEYGUARD:
if (!mDozingOnDown) {
- if (mUpdateMonitor.isFaceEnrolled()
- && !mUpdateMonitor.isFaceDetectionRunning()
- && !mUpdateMonitor.getUserCanSkipBouncer(
- KeyguardUpdateMonitor.getCurrentUser())) {
- mUpdateMonitor.requestFaceAuth(true,
- FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
+ mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false");
+ // Try triggering face auth, this "might" run. Check
+ // KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run.
+ boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+ FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
+
+ if (didFaceAuthRun) {
+ mUpdateMonitor.requestActiveUnlock(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
+ "lockScreenEmptySpaceTap");
} else {
mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
@@ -3873,11 +3947,6 @@ public final class NotificationPanelViewController {
.log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT);
startUnlockHintAnimation();
}
- if (mUpdateMonitor.isFaceEnrolled()) {
- mUpdateMonitor.requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
- "lockScreenEmptySpaceTap");
- }
}
return true;
case StatusBarState.SHADE_LOCKED:
@@ -4154,8 +4223,8 @@ public final class NotificationPanelViewController {
/**
* Sets the dozing state.
*
- * @param dozing {@code true} when dozing.
- * @param animate if transition should be animated.
+ * @param dozing {@code true} when dozing.
+ * @param animate if transition should be animated.
*/
public void setDozing(boolean dozing, boolean animate) {
if (dozing == mDozing) return;
@@ -4295,35 +4364,35 @@ public final class NotificationPanelViewController {
/**
* Starts fold to AOD animation.
*
- * @param startAction invoked when the animation starts.
- * @param endAction invoked when the animation finishes, also if it was cancelled.
+ * @param startAction invoked when the animation starts.
+ * @param endAction invoked when the animation finishes, also if it was cancelled.
* @param cancelAction invoked when the animation is cancelled, before endAction.
*/
public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
Runnable cancelAction) {
mView.animate()
- .translationX(0)
- .alpha(1f)
- .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
- .setInterpolator(EMPHASIZED_DECELERATE)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- startAction.run();
- }
+ .translationX(0)
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+ .setInterpolator(EMPHASIZED_DECELERATE)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ startAction.run();
+ }
- @Override
- public void onAnimationCancel(Animator animation) {
- cancelAction.run();
- }
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancelAction.run();
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- endAction.run();
- }
- }).setUpdateListener(anim -> {
- mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
- }).start();
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endAction.run();
+ }
+ }).setUpdateListener(anim -> {
+ mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
+ }).start();
}
/**
@@ -4621,14 +4690,16 @@ public final class NotificationPanelViewController {
Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
}
- private void notifyExpandingStarted() {
+ @VisibleForTesting
+ void notifyExpandingStarted() {
if (!mExpanding) {
mExpanding = true;
onExpandingStarted();
}
}
- private void notifyExpandingFinished() {
+ @VisibleForTesting
+ void notifyExpandingFinished() {
endClosing();
if (mExpanding) {
mExpanding = false;
@@ -4681,8 +4752,10 @@ public final class NotificationPanelViewController {
/**
* Maybe vibrate as panel is opened.
*
- * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead
- * being opened programmatically (such as by the open panel gesture), we always play haptic.
+ * @param openingWithTouch Whether the panel is being opened with touch. If the panel is
+ * instead
+ * being opened programmatically (such as by the open panel gesture), we
+ * always play haptic.
*/
private void maybeVibrateOnOpening(boolean openingWithTouch) {
if (mVibrateOnOpening) {
@@ -4728,6 +4801,7 @@ public final class NotificationPanelViewController {
mAmbientState.setSwipingUp(false);
if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
|| Math.abs(y - mInitialExpandY) > mTouchSlop
+ || (!isFullyExpanded() && !isFullyCollapsed())
|| event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
mVelocityTracker.computeCurrentVelocity(1000);
float vel = mVelocityTracker.getYVelocity();
@@ -4847,10 +4921,12 @@ public final class NotificationPanelViewController {
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
+
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
+
@Override
public void onAnimationEnd(Animator animation) {
mIsSpringBackAnimation = false;
@@ -4898,7 +4974,7 @@ public final class NotificationPanelViewController {
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
}
- mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+ mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> {
if (mExpandLatencyTracking && h != 0f) {
DejankUtils.postAfterTraversal(
() -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
@@ -5089,7 +5165,7 @@ public final class NotificationPanelViewController {
/**
* Create an animator that can also overshoot
*
- * @param targetHeight the target height
+ * @param targetHeight the target height
* @param overshootAmount the amount of overshoot desired
*/
private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
@@ -5125,7 +5201,8 @@ public final class NotificationPanelViewController {
*/
public void updatePanelExpansionAndVisibility() {
mShadeExpansionStateManager.onPanelExpansionChanged(
- mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ mExpandedFraction, isExpanded(),
+ mTracking, mExpansionDragDownAmountPx);
updateVisibility();
}
@@ -5886,7 +5963,7 @@ public final class NotificationPanelViewController {
public final class TouchHandler implements View.OnTouchListener {
private long mLastTouchDownTime = -1L;
- /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
+ /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
public boolean onInterceptTouchEvent(MotionEvent event) {
if (SPEW_LOGCAT) {
Log.v(TAG,
@@ -6051,6 +6128,10 @@ public final class NotificationPanelViewController {
mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event");
return true;
}
+ if (mPulsing) {
+ mShadeLog.logMotionEvent(event, "onTouch: eat touch, device pulsing");
+ return true;
+ }
if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
&& !mNotificationStackScrollLayoutController.isLongPressInProgress()
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
@@ -6073,7 +6154,7 @@ public final class NotificationPanelViewController {
}
handled |= handleTouch(event);
- return !mDozing || mPulsing || handled;
+ return !mDozing || handled;
}
private boolean handleTouch(MotionEvent event) {
@@ -6081,7 +6162,7 @@ public final class NotificationPanelViewController {
mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
return false;
}
- if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+ if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
return false;
}
@@ -6238,4 +6319,54 @@ public final class NotificationPanelViewController {
loadDimens();
}
}
+
+ static class SplitShadeTransitionAdapter extends Transition {
+ private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
+ private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
+
+ private final KeyguardStatusViewController mController;
+
+ SplitShadeTransitionAdapter(KeyguardStatusViewController controller) {
+ mController = controller;
+ }
+
+ private void captureValues(TransitionValues transitionValues) {
+ Rect boundsRect = new Rect();
+ boundsRect.left = transitionValues.view.getLeft();
+ boundsRect.top = transitionValues.view.getTop();
+ boundsRect.right = transitionValues.view.getRight();
+ boundsRect.bottom = transitionValues.view.getBottom();
+ transitionValues.values.put(PROP_BOUNDS, boundsRect);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+
+ Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
+ Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
+
+ anim.addUpdateListener(
+ animation -> mController.getClockAnimations().onPositionUpdated(
+ from, to, animation.getAnimatedFraction()));
+
+ return anim;
+ }
+
+ @Override
+ public String[] getTransitionProperties() {
+ return TRANSITION_PROPERTIES;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 1d9210592b78..66a22f4ddc0d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -135,7 +135,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
DumpManager dumpManager,
KeyguardStateController keyguardStateController,
ScreenOffAnimationController screenOffAnimationController,
- AuthController authController) {
+ AuthController authController,
+ ShadeExpansionStateManager shadeExpansionStateManager) {
mContext = context;
mWindowManager = windowManager;
mActivityManager = activityManager;
@@ -156,6 +157,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
.addCallback(mStateListener,
SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
configurationController.addCallback(this);
+ shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
float desiredPreferredRefreshRate = context.getResources()
.getInteger(R.integer.config_keyguardRefreshRate);
@@ -607,8 +609,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
apply(mCurrentState);
}
- @Override
- public void setQsExpanded(boolean expanded) {
+ private void onQsExpansionChanged(Boolean expanded) {
mCurrentState.mQsExpanded = expanded;
apply(mCurrentState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index d6f0de83ecc1..73c6d507f035 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -36,17 +36,12 @@ class NotificationsQSContainerController @Inject constructor(
private val navigationModeController: NavigationModeController,
private val overviewProxyService: OverviewProxyService,
private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController,
+ private val shadeExpansionStateManager: ShadeExpansionStateManager,
private val featureFlags: FeatureFlags,
@Main private val delayableExecutor: DelayableExecutor
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
- var qsExpanded = false
- set(value) {
- if (field != value) {
- field = value
- mView.invalidate()
- }
- }
+ private var qsExpanded = false
private var splitShadeEnabled = false
private var isQSDetailShowing = false
private var isQSCustomizing = false
@@ -71,6 +66,13 @@ class NotificationsQSContainerController @Inject constructor(
taskbarVisible = visible
}
}
+ private val shadeQsExpansionListener: ShadeQsExpansionListener =
+ ShadeQsExpansionListener { isQsExpanded ->
+ if (qsExpanded != isQsExpanded) {
+ qsExpanded = isQsExpanded
+ mView.invalidate()
+ }
+ }
// With certain configuration changes (like light/dark changes), the nav bar will disappear
// for a bit, causing `bottomStableInsets` to be unstable for some time. Debounce the value
@@ -106,6 +108,7 @@ class NotificationsQSContainerController @Inject constructor(
public override fun onViewAttached() {
updateResources()
overviewProxyService.addCallback(taskbarVisibilityListener)
+ shadeExpansionStateManager.addQsExpansionListener(shadeQsExpansionListener)
mView.setInsetsChangedListener(delayedInsetSetter)
mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
mView.setConfigurationChangedListener { updateResources() }
@@ -113,6 +116,7 @@ class NotificationsQSContainerController @Inject constructor(
override fun onViewDetached() {
overviewProxyService.removeCallback(taskbarVisibilityListener)
+ shadeExpansionStateManager.removeQsExpansionListener(shadeQsExpansionListener)
mView.removeOnInsetsChangedListener()
mView.removeQSFragmentAttachedListener()
mView.setConfigurationChangedListener(null)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index f617d471351e..7bba74a8b125 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -21,6 +21,7 @@ import android.util.Log
import androidx.annotation.FloatRange
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.Compile
+import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
/**
@@ -31,12 +32,14 @@ import javax.inject.Inject
@SysUISingleton
class ShadeExpansionStateManager @Inject constructor() {
- private val expansionListeners = mutableListOf<ShadeExpansionListener>()
- private val stateListeners = mutableListOf<ShadeStateListener>()
+ private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
+ private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
+ private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
@PanelState private var state: Int = STATE_CLOSED
@FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
private var expanded: Boolean = false
+ private var qsExpanded: Boolean = false
private var tracking: Boolean = false
private var dragDownPxAmount: Float = 0f
@@ -57,6 +60,15 @@ class ShadeExpansionStateManager @Inject constructor() {
expansionListeners.remove(listener)
}
+ fun addQsExpansionListener(listener: ShadeQsExpansionListener) {
+ qsExpansionListeners.add(listener)
+ listener.onQsExpansionChanged(qsExpanded)
+ }
+
+ fun removeQsExpansionListener(listener: ShadeQsExpansionListener) {
+ qsExpansionListeners.remove(listener)
+ }
+
/** Adds a listener that will be notified when the panel state has changed. */
fun addStateListener(listener: ShadeStateListener) {
stateListeners.add(listener)
@@ -126,6 +138,14 @@ class ShadeExpansionStateManager @Inject constructor() {
expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) }
}
+ /** Called when the quick settings expansion changes to fully expanded or collapsed. */
+ fun onQsExpansionChanged(qsExpanded: Boolean) {
+ this.qsExpanded = qsExpanded
+
+ debugLog("qsExpanded=$qsExpanded")
+ qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) }
+ }
+
/** Updates the panel state if necessary. */
fun updateState(@PanelState state: Int) {
debugLog(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index f1e44ce5736e..2b788d85a14c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -1,20 +1,17 @@
package com.android.systemui.shade
import android.view.MotionEvent
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.ShadeLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogMessage
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
private const val TAG = "systemui.shade"
/** Lightweight logging utility for the Shade. */
-class ShadeLogger @Inject constructor(
- @ShadeLog
- private val buffer: LogBuffer
-) {
+class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) {
fun v(@CompileTimeConstant msg: String) {
buffer.log(TAG, LogLevel.VERBOSE, msg)
}
@@ -28,21 +25,56 @@ class ShadeLogger @Inject constructor(
}
fun onQsInterceptMoveQsTrackingEnabled(h: Float) {
- log(LogLevel.VERBOSE,
+ log(
+ LogLevel.VERBOSE,
{ double1 = h.toDouble() },
- { "onQsIn[tercept: move action, QS tracking enabled. h = $double1" })
+ { "onQsIntercept: move action, QS tracking enabled. h = $double1" }
+ )
+ }
+
+ fun logQsTrackingNotStarted(
+ initialTouchY: Float,
+ y: Float,
+ h: Float,
+ touchSlop: Float,
+ qsExpanded: Boolean,
+ collapsedOnDown: Boolean,
+ keyguardShowing: Boolean,
+ qsExpansionEnabled: Boolean
+ ) {
+ log(
+ LogLevel.VERBOSE,
+ {
+ int1 = initialTouchY.toInt()
+ int2 = y.toInt()
+ long1 = h.toLong()
+ double1 = touchSlop.toDouble()
+ bool1 = qsExpanded
+ bool2 = collapsedOnDown
+ bool3 = keyguardShowing
+ bool4 = qsExpansionEnabled
+ },
+ {
+ "QsTrackingNotStarted: initTouchY=$int1,y=$int2,h=$long1,slop=$double1,qsExpanded" +
+ "=$bool1,collapsedDown=$bool2,keyguardShowing=$bool3,qsExpansion=$bool4"
+ }
+ )
}
fun logMotionEvent(event: MotionEvent, message: String) {
- log(LogLevel.VERBOSE, {
- str1 = message
- long1 = event.eventTime
- long2 = event.downTime
- int1 = event.action
- int2 = event.classification
- double1 = event.y.toDouble()
- }, {
- "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,classification=$int2"
- })
+ log(
+ LogLevel.VERBOSE,
+ {
+ str1 = message
+ long1 = event.eventTime
+ long2 = event.downTime
+ int1 = event.action
+ int2 = event.classification
+ double1 = event.y.toDouble()
+ },
+ {
+ "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
+ }
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt
new file mode 100644
index 000000000000..14882b9afd2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+/** A listener interface to be notified of expansion events for the quick settings panel. */
+fun interface ShadeQsExpansionListener {
+ /**
+ * Invoked whenever the quick settings expansion changes, when it is fully collapsed or expanded
+ */
+ fun onQsExpansionChanged(isQsExpanded: Boolean)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
new file mode 100644
index 000000000000..09019a69df47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.shade.data.repository
+
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.model.ShadeModel
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+
+/** Business logic for shade interactions */
+@SysUISingleton
+class ShadeRepository @Inject constructor(shadeExpansionStateManager: ShadeExpansionStateManager) {
+
+ val shadeModel: Flow<ShadeModel> =
+ conflatedCallbackFlow {
+ val callback =
+ object : ShadeExpansionListener {
+ override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
+ // Don't propagate ShadeExpansionChangeEvent.dragDownPxAmount field.
+ // It is too noisy and produces extra events that consumers won't care
+ // about
+ val info =
+ ShadeModel(
+ expansionAmount = event.fraction,
+ isExpanded = event.expanded,
+ isUserDragging = event.tracking
+ )
+ trySendWithFailureLogging(info, TAG, "updated shade expansion info")
+ }
+ }
+
+ shadeExpansionStateManager.addExpansionListener(callback)
+ trySendWithFailureLogging(ShadeModel(), TAG, "initial shade expansion info")
+
+ awaitClose { shadeExpansionStateManager.removeExpansionListener(callback) }
+ }
+ .distinctUntilChanged()
+
+ companion object {
+ private const val TAG = "ShadeRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
new file mode 100644
index 000000000000..ce0f4283ff83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.shade.domain.model
+
+import android.annotation.FloatRange
+
+/** Information about shade (NotificationPanel) expansion */
+data class ShadeModel(
+ /** 0 when collapsed, 1 when fully expanded. */
+ @FloatRange(from = 0.0, to = 1.0) val expansionAmount: Float = 0f,
+ /** Whether the panel should be considered expanded */
+ val isExpanded: Boolean = false,
+ /** Whether the user is actively dragging the panel. */
+ val isUserDragging: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
index 7f7ff9cf4881..90c52bd8c9f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.statusbar
import android.app.PendingIntent
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 04621168493b..e6d7e4124d01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -69,12 +69,15 @@ import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.GcUtils;
import com.android.internal.view.AppearanceRegion;
+import com.android.systemui.dump.DumpHandler;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.tracing.ProtoTracer;
+import java.io.FileDescriptor;
import java.io.FileOutputStream;
+import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -184,6 +187,7 @@ public class CommandQueue extends IStatusBar.Stub implements
private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
private ProtoTracer mProtoTracer;
private final @Nullable CommandRegistry mRegistry;
+ private final @Nullable DumpHandler mDumpHandler;
/**
* These methods are called back on the main thread.
@@ -473,12 +477,18 @@ public class CommandQueue extends IStatusBar.Stub implements
}
public CommandQueue(Context context) {
- this(context, null, null);
+ this(context, null, null, null);
}
- public CommandQueue(Context context, ProtoTracer protoTracer, CommandRegistry registry) {
+ public CommandQueue(
+ Context context,
+ ProtoTracer protoTracer,
+ CommandRegistry registry,
+ DumpHandler dumpHandler
+ ) {
mProtoTracer = protoTracer;
mRegistry = registry;
+ mDumpHandler = dumpHandler;
context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler);
// We always have default display.
setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE);
@@ -1178,6 +1188,35 @@ public class CommandQueue extends IStatusBar.Stub implements
}
@Override
+ public void dumpProto(String[] args, ParcelFileDescriptor pfd) {
+ final FileDescriptor fd = pfd.getFileDescriptor();
+ // This is mimicking Binder#dumpAsync, but on this side of the binder. Might be possible
+ // to just throw this work onto the handler just like the other messages
+ Thread thr = new Thread("Sysui.dumpProto") {
+ public void run() {
+ try {
+ if (mDumpHandler == null) {
+ return;
+ }
+ // We won't be using the PrintWriter.
+ OutputStream o = new OutputStream() {
+ @Override
+ public void write(int b) {}
+ };
+ mDumpHandler.dump(fd, new PrintWriter(o), args);
+ } finally {
+ try {
+ // Close the file descriptor so the TransferPipe finishes its thread
+ pfd.close();
+ } catch (Exception e) {
+ }
+ }
+ }
+ };
+ thr.start();
+ }
+
+ @Override
public void runGcForTest() {
// Gc sysui
GcUtils.runGcAndFinalizersSync();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
index 886ad684649f..5fb500247697 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
@@ -5,7 +5,7 @@ import android.util.IndentingPrintWriter
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.media.controls.ui.MediaHierarchyManager
import com.android.systemui.shade.NotificationPanelViewController
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.assisted.Assisted
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 80069319601f..a2e4536ce45f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -24,7 +24,7 @@ import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.media.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QS
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 4be5a1aa0215..ced725e0b1d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -48,9 +48,9 @@ import com.android.systemui.animation.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaData;
-import com.android.systemui.media.MediaDataManager;
-import com.android.systemui.media.SmartspaceMediaData;
+import com.android.systemui.media.controls.models.player.MediaData;
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 0c9e1ec1ff77..e21acb7e0f68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -92,9 +92,6 @@ public interface NotificationShadeWindowController extends RemoteInputController
/** Sets the state of whether the keyguard is fading away or not. */
default void setKeyguardFadingAway(boolean keyguardFadingAway) {}
- /** Sets the state of whether the quick settings is expanded or not. */
- default void setQsExpanded(boolean expanded) {}
-
/** Sets the state of whether the user activities are forced or not. */
default void setForceUserActivity(boolean forceUserActivity) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index f96198450ed6..87ef92a28d5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -40,6 +40,7 @@ import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -110,8 +111,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
setClipChildren(false);
setClipToPadding(false);
mShelfIcons.setIsStaticLayout(false);
- setBottomRoundness(1.0f, false /* animate */);
- setTopRoundness(1f, false /* animate */);
+ requestBottomRoundness(1.0f, /* animate = */ false, SourceType.DefaultValue);
+ requestTopRoundness(1f, false, SourceType.DefaultValue);
// Setting this to first in section to get the clipping to the top roundness correct. This
// value determines the way we are clipping to the top roundness of the overall shade
@@ -413,7 +414,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
// only if the first icon is fully in the shelf we want to clip to it!
backgroundTop = (int) (child.getTranslationY() - getTranslationY());
- firstElementRoundness = expandableRow.getCurrentTopRoundness();
+ firstElementRoundness = expandableRow.getTopRoundness();
}
}
@@ -507,28 +508,36 @@ public class NotificationShelf extends ActivatableNotificationView implements
// Round bottom corners within animation bounds
final float changeFraction = MathUtils.saturate(
(viewEnd - cornerAnimationTop) / cornerAnimationDistance);
- anv.setBottomRoundness(anv.isLastInSection() ? 1f : changeFraction,
- false /* animate */);
+ anv.requestBottomRoundness(
+ anv.isLastInSection() ? 1f : changeFraction,
+ /* animate = */ false,
+ SourceType.OnScroll);
} else if (viewEnd < cornerAnimationTop) {
// Fast scroll skips frames and leaves corners with unfinished rounding.
// Reset top and bottom corners outside of animation bounds.
- anv.setBottomRoundness(anv.isLastInSection() ? 1f : smallCornerRadius,
- false /* animate */);
+ anv.requestBottomRoundness(
+ anv.isLastInSection() ? 1f : smallCornerRadius,
+ /* animate = */ false,
+ SourceType.OnScroll);
}
if (viewStart >= cornerAnimationTop) {
// Round top corners within animation bounds
final float changeFraction = MathUtils.saturate(
(viewStart - cornerAnimationTop) / cornerAnimationDistance);
- anv.setTopRoundness(anv.isFirstInSection() ? 1f : changeFraction,
- false /* animate */);
+ anv.requestTopRoundness(
+ anv.isFirstInSection() ? 1f : changeFraction,
+ false,
+ SourceType.OnScroll);
} else if (viewStart < cornerAnimationTop) {
// Fast scroll skips frames and leaves corners with unfinished rounding.
// Reset top and bottom corners outside of animation bounds.
- anv.setTopRoundness(anv.isFirstInSection() ? 1f : smallCornerRadius,
- false /* animate */);
+ anv.requestTopRoundness(
+ anv.isFirstInSection() ? 1f : smallCornerRadius,
+ false,
+ SourceType.OnScroll);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index bbff0466cf50..c630feba1dcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -39,6 +39,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -52,7 +53,10 @@ import javax.inject.Inject
import kotlin.math.max
/**
- * A utility class to enable the downward swipe on when pulsing.
+ * A utility class that handles notification panel expansion when a user swipes downward on a
+ * notification from the pulsing state.
+ * If face-bypass is enabled, the user can swipe down anywhere on the screen (not just from a
+ * notification) to trigger the notification panel expansion.
*/
@SysUISingleton
class PulseExpansionHandler @Inject
@@ -62,9 +66,10 @@ constructor(
private val bypassController: KeyguardBypassController,
private val headsUpManager: HeadsUpManagerPhone,
private val roundnessManager: NotificationRoundnessManager,
- private val configurationController: ConfigurationController,
+ configurationController: ConfigurationController,
private val statusBarStateController: StatusBarStateController,
private val falsingManager: FalsingManager,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
private val falsingCollector: FalsingCollector,
dumpManager: DumpManager
@@ -123,6 +128,13 @@ constructor(
initResources(context)
}
})
+
+ shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
+ if (qsExpanded != isQsExpanded) {
+ qsExpanded = isQsExpanded
+ }
+ }
+
mPowerManager = context.getSystemService(PowerManager::class.java)
dumpManager.registerDumpable(this)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index b3dd853cd2e1..402217dac185 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -71,9 +71,9 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.ConfigurationController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 11e3d1773c4c..eacb18e3c50c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -29,8 +29,9 @@ import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.carrier.QSCarrierGroupController;
@@ -181,8 +182,10 @@ public interface CentralSurfacesDependenciesModule {
static CommandQueue provideCommandQueue(
Context context,
ProtoTracer protoTracer,
- CommandRegistry registry) {
- return new CommandQueue(context, protoTracer, registry);
+ CommandRegistry registry,
+ DumpHandler dumpHandler
+ ) {
+ return new CommandQueue(context, protoTracer, registry, dumpHandler);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index d88f07ca304c..737b4812d4fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -25,11 +25,12 @@ import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import com.android.internal.annotations.GuardedBy
-import com.android.systemui.animation.Interpolators
import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
@@ -42,7 +43,6 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE
import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
import com.android.systemui.util.leak.RotationUtils.Rotation
-
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -67,7 +67,8 @@ class PrivacyDotViewController @Inject constructor(
private val stateController: StatusBarStateController,
private val configurationController: ConfigurationController,
private val contentInsetsProvider: StatusBarContentInsetsProvider,
- private val animationScheduler: SystemStatusAnimationScheduler
+ private val animationScheduler: SystemStatusAnimationScheduler,
+ shadeExpansionStateManager: ShadeExpansionStateManager
) {
private lateinit var tl: View
private lateinit var tr: View
@@ -128,6 +129,13 @@ class PrivacyDotViewController @Inject constructor(
updateStatusBarState()
}
})
+
+ shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
+ dlog("setQsExpanded $isQsExpanded")
+ synchronized(lock) {
+ nextViewState = nextViewState.copy(qsExpanded = isQsExpanded)
+ }
+ }
}
fun setUiExecutor(e: DelayableExecutor) {
@@ -138,13 +146,6 @@ class PrivacyDotViewController @Inject constructor(
showingListener = l
}
- fun setQsExpanded(expanded: Boolean) {
- dlog("setQsExpanded $expanded")
- synchronized(lock) {
- nextViewState = nextViewState.copy(qsExpanded = expanded)
- }
- }
-
@UiThread
fun setNewRotation(rot: Int) {
dlog("updateRotation: $rot")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
index 17feaa842165..9bdff928c44b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.gesture
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.SwipeStatusBarAwayLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** Log messages for [SwipeStatusBarAwayGestureHandler]. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 8f8813b80b5f..842204bbf621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -118,6 +118,7 @@ class LockscreenSmartspaceController @Inject constructor(
regionSamplingEnabled,
updateFun
)
+ initializeTextColors(regionSamplingInstance)
regionSamplingInstance.startRegionSampler()
regionSamplingInstances.put(v, regionSamplingInstance)
connectSession()
@@ -361,18 +362,20 @@ class LockscreenSmartspaceController @Inject constructor(
}
}
+ private fun initializeTextColors(regionSamplingInstance: RegionSamplingInstance) {
+ val lightThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_LightWallpaper)
+ val darkColor = Utils.getColorAttrDefaultColor(lightThemeContext, R.attr.wallpaperTextColor)
+
+ val darkThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI)
+ val lightColor = Utils.getColorAttrDefaultColor(darkThemeContext, R.attr.wallpaperTextColor)
+
+ regionSamplingInstance.setForegroundColors(lightColor, darkColor)
+ }
+
private fun updateTextColorFromRegionSampler() {
smartspaceViews.forEach {
- val isRegionDark = regionSamplingInstances.getValue(it).currentRegionDarkness()
- val themeID = if (isRegionDark.isDark) {
- R.style.Theme_SystemUI
- } else {
- R.style.Theme_SystemUI_LightWallpaper
- }
- val themedContext = ContextThemeWrapper(context, themeID)
- val wallpaperTextColor =
- Utils.getColorAttrDefaultColor(themedContext, R.attr.wallpaperTextColor)
- it.setPrimaryTextColor(wallpaperTextColor)
+ val textColor = regionSamplingInstances.getValue(it).currentForegroundColor()
+ it.setPrimaryTextColor(textColor)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 822840dfd86b..0a5e9867a17f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -290,7 +290,7 @@ public class InstantAppNotifier
.setComponent(aiaComponent)
.setAction(Intent.ACTION_VIEW)
.addCategory(Intent.CATEGORY_BROWSABLE)
- .addCategory("unique:" + System.currentTimeMillis())
+ .setIdentifier("unique:" + System.currentTimeMillis())
.putExtra(Intent.EXTRA_PACKAGE_NAME, appInfo.packageName)
.putExtra(
Intent.EXTRA_VERSION_CODE,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 7fbdd35796c1..2734511de78c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -17,40 +17,27 @@
package com.android.systemui.statusbar.notification
import android.content.Context
-import android.util.Log
-import android.widget.Toast
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.util.Compile
import javax.inject.Inject
class NotifPipelineFlags @Inject constructor(
val context: Context,
val featureFlags: FeatureFlags
) {
- fun checkLegacyPipelineEnabled(): Boolean {
- if (Compile.IS_DEBUG) {
- Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show()
- }
- if (featureFlags.isEnabled(Flags.NEW_PIPELINE_CRASH_ON_CALL_TO_OLD_PIPELINE)) {
- throw RuntimeException("Old pipeline code running with new pipeline enabled")
- } else {
- Log.d("NotifPipeline", "Old pipeline code running with new pipeline enabled",
- Exception())
- }
- return false
- }
-
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
- fun isSmartspaceDedupingEnabled(): Boolean =
- featureFlags.isEnabled(Flags.SMARTSPACE) &&
- featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING)
-
- fun removeUnrankedNotifs(): Boolean =
- featureFlags.isEnabled(Flags.REMOVE_UNRANKED_NOTIFICATIONS)
+ fun isSmartspaceDedupingEnabled(): Boolean = featureFlags.isEnabled(Flags.SMARTSPACE)
fun fullScreenIntentRequiresKeyguard(): Boolean =
featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD)
+
+ val isStabilityIndexFixEnabled: Boolean by lazy {
+ featureFlags.isEnabled(Flags.STABILITY_INDEX_FIX)
+ }
+
+ val isSemiStableSortEnabled: Boolean by lazy {
+ featureFlags.isEnabled(Flags.SEMI_STABLE_SORT)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
index ad3dfedcdb96..3058fbbc1031 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 553826dda919..0d35fdce953e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -70,8 +70,8 @@ class NotificationLaunchAnimatorController(
val height = max(0, notification.actualHeight - notification.clipBottomAmount)
val location = notification.locationOnScreen
- val clipStartLocation = notificationListContainer.getTopClippingStartLocation()
- val roundedTopClipping = Math.max(clipStartLocation - location[1], 0)
+ val clipStartLocation = notificationListContainer.topClippingStartLocation
+ val roundedTopClipping = (clipStartLocation - location[1]).coerceAtLeast(0)
val windowTop = location[1] + roundedTopClipping
val topCornerRadius = if (roundedTopClipping > 0) {
// Because the rounded Rect clipping is complex, we start the top rounding at
@@ -80,7 +80,7 @@ class NotificationLaunchAnimatorController(
// if we'd like to have this perfect, but this is close enough.
0f
} else {
- notification.currentBackgroundRadiusTop
+ notification.topCornerRadius
}
val params = LaunchAnimationParameters(
top = windowTop,
@@ -88,7 +88,7 @@ class NotificationLaunchAnimatorController(
left = location[0],
right = location[0] + notification.width,
topCornerRadius = topCornerRadius,
- bottomCornerRadius = notification.currentBackgroundRadiusBottom
+ bottomCornerRadius = notification.bottomCornerRadius
)
params.startTranslationZ = notification.translationZ
@@ -97,8 +97,8 @@ class NotificationLaunchAnimatorController(
params.startClipTopAmount = notification.clipTopAmount
if (notification.isChildInGroup) {
params.startNotificationTop += notification.notificationParent.translationY
- val parentRoundedClip = Math.max(
- clipStartLocation - notification.notificationParent.locationOnScreen[1], 0)
+ val locationOnScreen = notification.notificationParent.locationOnScreen[1]
+ val parentRoundedClip = (clipStartLocation - locationOnScreen).coerceAtLeast(0)
params.parentStartRoundedTopClipping = parentRoundedClip
val parentClip = notification.notificationParent.clipTopAmount
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 7242506f1015..d97b712df030 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -18,8 +18,10 @@ package com.android.systemui.statusbar.notification
import android.animation.ObjectAnimator
import android.util.FloatProperty
+import com.android.systemui.Dumpable
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
@@ -32,17 +34,20 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.min
@SysUISingleton
class NotificationWakeUpCoordinator @Inject constructor(
+ dumpManager: DumpManager,
private val mHeadsUpManager: HeadsUpManager,
private val statusBarStateController: StatusBarStateController,
private val bypassController: KeyguardBypassController,
private val dozeParameters: DozeParameters,
private val screenOffAnimationController: ScreenOffAnimationController
-) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener {
+) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener,
+ Dumpable {
private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
"notificationVisibility") {
@@ -60,6 +65,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
private var mLinearDozeAmount: Float = 0.0f
private var mDozeAmount: Float = 0.0f
+ private var mDozeAmountSource: String = "init"
private var mNotificationVisibleAmount = 0.0f
private var mNotificationsVisible = false
private var mNotificationsVisibleForExpansion = false
@@ -142,6 +148,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
}
init {
+ dumpManager.registerDumpable(this)
mHeadsUpManager.addListener(this)
statusBarStateController.addCallback(this)
addListener(object : WakeUpListener {
@@ -248,13 +255,14 @@ class NotificationWakeUpCoordinator @Inject constructor(
// Let's notify the scroller that an animation started
notifyAnimationStart(mLinearDozeAmount == 1.0f)
}
- setDozeAmount(linear, eased)
+ setDozeAmount(linear, eased, source = "StatusBar")
}
- fun setDozeAmount(linear: Float, eased: Float) {
+ fun setDozeAmount(linear: Float, eased: Float, source: String) {
val changed = linear != mLinearDozeAmount
mLinearDozeAmount = linear
mDozeAmount = eased
+ mDozeAmountSource = source
mStackScrollerController.setDozeAmount(mDozeAmount)
updateHideAmount()
if (changed && linear == 0.0f) {
@@ -271,7 +279,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
// undefined state, so it's an indication that we should do state cleanup. We override
// the doze amount to 0f (not dozing) so that the notifications are no longer hidden.
// See: UnlockedScreenOffAnimationController.onFinishedWakingUp()
- setDozeAmount(0f, 0f)
+ setDozeAmount(0f, 0f, source = "Override: Shade->Shade (lock cancelled by unlock)")
}
if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) {
@@ -311,12 +319,11 @@ class NotificationWakeUpCoordinator @Inject constructor(
*/
private fun overrideDozeAmountIfBypass(): Boolean {
if (bypassController.bypassEnabled) {
- var amount = 1.0f
- if (statusBarStateController.state == StatusBarState.SHADE ||
- statusBarStateController.state == StatusBarState.SHADE_LOCKED) {
- amount = 0.0f
+ if (statusBarStateController.state == StatusBarState.KEYGUARD) {
+ setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)")
+ } else {
+ setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
}
- setDozeAmount(amount, amount)
return true
}
return false
@@ -332,7 +339,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
*/
private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) {
- setDozeAmount(1f, 1f)
+ setDozeAmount(1f, 1f, source = "Override: animating screen off")
return true
}
@@ -414,6 +421,26 @@ class NotificationWakeUpCoordinator @Inject constructor(
private fun shouldAnimateVisibility() =
dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("mLinearDozeAmount: $mLinearDozeAmount")
+ pw.println("mDozeAmount: $mDozeAmount")
+ pw.println("mDozeAmountSource: $mDozeAmountSource")
+ pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount")
+ pw.println("mNotificationsVisible: $mNotificationsVisible")
+ pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion")
+ pw.println("mVisibilityAmount: $mVisibilityAmount")
+ pw.println("mLinearVisibilityAmount: $mLinearVisibilityAmount")
+ pw.println("pulseExpanding: $pulseExpanding")
+ pw.println("state: ${StatusBarState.toString(state)}")
+ pw.println("fullyAwake: $fullyAwake")
+ pw.println("wakingUp: $wakingUp")
+ pw.println("willWakeUp: $willWakeUp")
+ pw.println("collapsedEnoughToHide: $collapsedEnoughToHide")
+ pw.println("pulsing: $pulsing")
+ pw.println("notificationsFullyHidden: $notificationsFullyHidden")
+ pw.println("canShowPulsingHuns: $canShowPulsingHuns")
+ }
+
interface WakeUpListener {
/**
* Called whenever the notifications are fully hidden or shown
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
new file mode 100644
index 000000000000..ed7f648081c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -0,0 +1,284 @@
+package com.android.systemui.statusbar.notification
+
+import android.util.FloatProperty
+import android.view.View
+import androidx.annotation.FloatRange
+import com.android.systemui.R
+import com.android.systemui.statusbar.notification.stack.AnimationProperties
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import kotlin.math.abs
+
+/**
+ * Interface that allows to request/retrieve top and bottom roundness (a value between 0f and 1f).
+ *
+ * To request a roundness value, an [SourceType] must be specified. In case more origins require
+ * different roundness, for the same property, the maximum value will always be chosen.
+ *
+ * It also returns the current radius for all corners ([updatedRadii]).
+ */
+interface Roundable {
+ /** Properties required for a Roundable */
+ val roundableState: RoundableState
+
+ /** Current top roundness */
+ @get:FloatRange(from = 0.0, to = 1.0)
+ @JvmDefault
+ val topRoundness: Float
+ get() = roundableState.topRoundness
+
+ /** Current bottom roundness */
+ @get:FloatRange(from = 0.0, to = 1.0)
+ @JvmDefault
+ val bottomRoundness: Float
+ get() = roundableState.bottomRoundness
+
+ /** Max radius in pixel */
+ @JvmDefault
+ val maxRadius: Float
+ get() = roundableState.maxRadius
+
+ /** Current top corner in pixel, based on [topRoundness] and [maxRadius] */
+ @JvmDefault
+ val topCornerRadius: Float
+ get() = topRoundness * maxRadius
+
+ /** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
+ @JvmDefault
+ val bottomCornerRadius: Float
+ get() = bottomRoundness * maxRadius
+
+ /** Get and update the current radii */
+ @JvmDefault
+ val updatedRadii: FloatArray
+ get() =
+ roundableState.radiiBuffer.also { radii ->
+ updateRadii(
+ topCornerRadius = topCornerRadius,
+ bottomCornerRadius = bottomCornerRadius,
+ radii = radii,
+ )
+ }
+
+ /**
+ * Request the top roundness [value] for a specific [sourceType].
+ *
+ * The top roundness of a [Roundable] can be defined by different [sourceType]. In case more
+ * origins require different roundness, for the same property, the maximum value will always be
+ * chosen.
+ *
+ * @param value a value between 0f and 1f.
+ * @param animate true if it should animate to that value.
+ * @param sourceType the source from which the request for roundness comes.
+ * @return Whether the roundness was changed.
+ */
+ @JvmDefault
+ fun requestTopRoundness(
+ @FloatRange(from = 0.0, to = 1.0) value: Float,
+ animate: Boolean,
+ sourceType: SourceType,
+ ): Boolean {
+ val roundnessMap = roundableState.topRoundnessMap
+ val lastValue = roundnessMap.values.maxOrNull() ?: 0f
+ if (value == 0f) {
+ // we should only take the largest value, and since the smallest value is 0f, we can
+ // remove this value from the list. In the worst case, the list is empty and the
+ // default value is 0f.
+ roundnessMap.remove(sourceType)
+ } else {
+ roundnessMap[sourceType] = value
+ }
+ val newValue = roundnessMap.values.maxOrNull() ?: 0f
+
+ if (lastValue != newValue) {
+ val wasAnimating = roundableState.isTopAnimating()
+
+ // Fail safe:
+ // when we've been animating previously and we're now getting an update in the
+ // other direction, make sure to animate it too, otherwise, the localized updating
+ // may make the start larger than 1.0.
+ val shouldAnimate = wasAnimating && abs(newValue - lastValue) > 0.5f
+
+ roundableState.setTopRoundness(value = newValue, animated = shouldAnimate || animate)
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Request the bottom roundness [value] for a specific [sourceType].
+ *
+ * The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more
+ * origins require different roundness, for the same property, the maximum value will always be
+ * chosen.
+ *
+ * @param value value between 0f and 1f.
+ * @param animate true if it should animate to that value.
+ * @param sourceType the source from which the request for roundness comes.
+ * @return Whether the roundness was changed.
+ */
+ @JvmDefault
+ fun requestBottomRoundness(
+ @FloatRange(from = 0.0, to = 1.0) value: Float,
+ animate: Boolean,
+ sourceType: SourceType,
+ ): Boolean {
+ val roundnessMap = roundableState.bottomRoundnessMap
+ val lastValue = roundnessMap.values.maxOrNull() ?: 0f
+ if (value == 0f) {
+ // we should only take the largest value, and since the smallest value is 0f, we can
+ // remove this value from the list. In the worst case, the list is empty and the
+ // default value is 0f.
+ roundnessMap.remove(sourceType)
+ } else {
+ roundnessMap[sourceType] = value
+ }
+ val newValue = roundnessMap.values.maxOrNull() ?: 0f
+
+ if (lastValue != newValue) {
+ val wasAnimating = roundableState.isBottomAnimating()
+
+ // Fail safe:
+ // when we've been animating previously and we're now getting an update in the
+ // other direction, make sure to animate it too, otherwise, the localized updating
+ // may make the start larger than 1.0.
+ val shouldAnimate = wasAnimating && abs(newValue - lastValue) > 0.5f
+
+ roundableState.setBottomRoundness(value = newValue, animated = shouldAnimate || animate)
+ return true
+ }
+ return false
+ }
+
+ /** Apply the roundness changes, usually means invalidate the [RoundableState.targetView]. */
+ @JvmDefault
+ fun applyRoundness() {
+ roundableState.targetView.invalidate()
+ }
+
+ /** @return true if top or bottom roundness is not zero. */
+ @JvmDefault
+ fun hasRoundedCorner(): Boolean {
+ return topRoundness != 0f || bottomRoundness != 0f
+ }
+
+ /**
+ * Update an Array of 8 values, 4 pairs of [X,Y] radii. As expected by param radii of
+ * [android.graphics.Path.addRoundRect].
+ *
+ * This method reuses the previous [radii] for performance reasons.
+ */
+ @JvmDefault
+ fun updateRadii(
+ topCornerRadius: Float,
+ bottomCornerRadius: Float,
+ radii: FloatArray,
+ ) {
+ if (radii.size != 8) error("Unexpected radiiBuffer size ${radii.size}")
+
+ if (radii[0] != topCornerRadius || radii[4] != bottomCornerRadius) {
+ (0..3).forEach { radii[it] = topCornerRadius }
+ (4..7).forEach { radii[it] = bottomCornerRadius }
+ }
+ }
+}
+
+/**
+ * State object for a `Roundable` class.
+ * @param targetView Will handle the [AnimatableProperty]
+ * @param roundable Target of the radius animation
+ * @param maxRadius Max corner radius in pixels
+ */
+class RoundableState(
+ internal val targetView: View,
+ roundable: Roundable,
+ internal val maxRadius: Float,
+) {
+ /** Animatable for top roundness */
+ private val topAnimatable = topAnimatable(roundable)
+
+ /** Animatable for bottom roundness */
+ private val bottomAnimatable = bottomAnimatable(roundable)
+
+ /** Current top roundness. Use [setTopRoundness] to update this value */
+ @set:FloatRange(from = 0.0, to = 1.0)
+ internal var topRoundness = 0f
+ private set
+
+ /** Current bottom roundness. Use [setBottomRoundness] to update this value */
+ @set:FloatRange(from = 0.0, to = 1.0)
+ internal var bottomRoundness = 0f
+ private set
+
+ /** Last requested top roundness associated by [SourceType] */
+ internal val topRoundnessMap = mutableMapOf<SourceType, Float>()
+
+ /** Last requested bottom roundness associated by [SourceType] */
+ internal val bottomRoundnessMap = mutableMapOf<SourceType, Float>()
+
+ /** Last cached radii */
+ internal val radiiBuffer = FloatArray(8)
+
+ /** Is top roundness animation in progress? */
+ internal fun isTopAnimating() = PropertyAnimator.isAnimating(targetView, topAnimatable)
+
+ /** Is bottom roundness animation in progress? */
+ internal fun isBottomAnimating() = PropertyAnimator.isAnimating(targetView, bottomAnimatable)
+
+ /** Set the current top roundness */
+ internal fun setTopRoundness(
+ value: Float,
+ animated: Boolean = targetView.isShown,
+ ) {
+ PropertyAnimator.setProperty(targetView, topAnimatable, value, DURATION, animated)
+ }
+
+ /** Set the current bottom roundness */
+ internal fun setBottomRoundness(
+ value: Float,
+ animated: Boolean = targetView.isShown,
+ ) {
+ PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated)
+ }
+
+ companion object {
+ private val DURATION: AnimationProperties =
+ AnimationProperties()
+ .setDuration(StackStateAnimator.ANIMATION_DURATION_CORNER_RADIUS.toLong())
+
+ private fun topAnimatable(roundable: Roundable): AnimatableProperty =
+ AnimatableProperty.from(
+ object : FloatProperty<View>("topRoundness") {
+ override fun get(view: View): Float = roundable.topRoundness
+
+ override fun setValue(view: View, value: Float) {
+ roundable.roundableState.topRoundness = value
+ roundable.applyRoundness()
+ }
+ },
+ R.id.top_roundess_animator_tag,
+ R.id.top_roundess_animator_end_tag,
+ R.id.top_roundess_animator_start_tag,
+ )
+
+ private fun bottomAnimatable(roundable: Roundable): AnimatableProperty =
+ AnimatableProperty.from(
+ object : FloatProperty<View>("bottomRoundness") {
+ override fun get(view: View): Float = roundable.bottomRoundness
+
+ override fun setValue(view: View, value: Float) {
+ roundable.roundableState.bottomRoundness = value
+ roundable.applyRoundness()
+ }
+ },
+ R.id.bottom_roundess_animator_tag,
+ R.id.bottom_roundess_animator_end_tag,
+ R.id.bottom_roundess_animator_start_tag,
+ )
+ }
+}
+
+enum class SourceType {
+ DefaultValue,
+ OnDismissAnimation,
+ OnScroll,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index f8449ae8807b..84ab0d1190f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -68,6 +68,9 @@ data class ListAttachState private constructor(
*/
var stableIndex: Int = -1
+ /** Access the index of the [section] or -1 if the entry does not have one */
+ val sectionIndex: Int get() = section?.index ?: -1
+
/** Copies the state of another instance. */
fun clone(other: ListAttachState) {
parent = other.parent
@@ -95,11 +98,13 @@ data class ListAttachState private constructor(
* This can happen if the entry is removed from a group that was broken up or if the entry was
* filtered out during any of the filtering steps.
*/
- fun detach() {
+ fun detach(includingStableIndex: Boolean) {
parent = null
section = null
promoter = null
- // stableIndex = -1 // TODO(b/241229236): Clear this once we fix the stability fragility
+ if (includingStableIndex) {
+ stableIndex = -1
+ }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 2887f975d46c..df35c9e6832a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -602,7 +602,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
mInconsistencyTracker.logNewMissingNotifications(rankingMap);
mInconsistencyTracker.logNewInconsistentRankings(currentEntriesWithoutRankings, rankingMap);
- if (currentEntriesWithoutRankings != null && mNotifPipelineFlags.removeUnrankedNotifs()) {
+ if (currentEntriesWithoutRankings != null) {
for (NotificationEntry entry : currentEntriesWithoutRankings.values()) {
entry.mCancellationReason = REASON_UNKNOWN;
tryRemoveNotification(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index e129ee45817a..3ae2545e4e10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -54,6 +54,9 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState;
+import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort;
+import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort.StableOrder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper;
import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
@@ -96,11 +99,14 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
// used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated
// TODO replace temp with collection pool for readability
private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>();
+ private NotifPipelineFlags mFlags;
private final boolean mAlwaysLogList;
private List<ListEntry> mNotifList = new ArrayList<>();
private List<ListEntry> mNewNotifList = new ArrayList<>();
+ private final SemiStableSort mSemiStableSort = new SemiStableSort();
+ private final StableOrder<ListEntry> mStableOrder = this::getStableOrderRank;
private final PipelineState mPipelineState = new PipelineState();
private final Map<String, GroupEntry> mGroups = new ArrayMap<>();
private Collection<NotificationEntry> mAllEntries = Collections.emptyList();
@@ -141,6 +147,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
) {
mSystemClock = systemClock;
mLogger = logger;
+ mFlags = flags;
mAlwaysLogList = flags.isDevLoggingEnabled();
mInteractionTracker = interactionTracker;
mChoreographer = pipelineChoreographer;
@@ -527,7 +534,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
List<NotifFilter> filters) {
Trace.beginSection("ShadeListBuilder.filterNotifs");
final long now = mSystemClock.uptimeMillis();
- for (ListEntry entry : entries) {
+ for (ListEntry entry : entries) {
if (entry instanceof GroupEntry) {
final GroupEntry groupEntry = (GroupEntry) entry;
@@ -958,7 +965,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
* filtered out during any of the filtering steps.
*/
private void annulAddition(ListEntry entry) {
- entry.getAttachState().detach();
+ // NOTE(b/241229236): Don't clear stableIndex until we fix stability fragility
+ entry.getAttachState().detach(/* includingStableIndex= */ mFlags.isSemiStableSortEnabled());
}
private void assignSections() {
@@ -978,7 +986,16 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
private void sortListAndGroups() {
Trace.beginSection("ShadeListBuilder.sortListAndGroups");
- // Assign sections to top-level elements and sort their children
+ if (mFlags.isSemiStableSortEnabled()) {
+ sortWithSemiStableSort();
+ } else {
+ sortWithLegacyStability();
+ }
+ Trace.endSection();
+ }
+
+ private void sortWithLegacyStability() {
+ // Sort all groups and the top level list
for (ListEntry entry : mNotifList) {
if (entry instanceof GroupEntry) {
GroupEntry parent = (GroupEntry) entry;
@@ -991,16 +1008,15 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
// Check for suppressed order changes
if (!getStabilityManager().isEveryChangeAllowed()) {
mForceReorderable = true;
- boolean isSorted = isShadeSorted();
+ boolean isSorted = isShadeSortedLegacy();
mForceReorderable = false;
if (!isSorted) {
getStabilityManager().onEntryReorderSuppressed();
}
}
- Trace.endSection();
}
- private boolean isShadeSorted() {
+ private boolean isShadeSortedLegacy() {
if (!isSorted(mNotifList, mTopLevelComparator)) {
return false;
}
@@ -1014,6 +1030,43 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
return true;
}
+ private void sortWithSemiStableSort() {
+ // Sort each group's children
+ boolean allSorted = true;
+ for (ListEntry entry : mNotifList) {
+ if (entry instanceof GroupEntry) {
+ GroupEntry parent = (GroupEntry) entry;
+ allSorted &= sortGroupChildren(parent.getRawChildren());
+ }
+ }
+ // Sort each section within the top level list
+ mNotifList.sort(mTopLevelComparator);
+ if (!getStabilityManager().isEveryChangeAllowed()) {
+ for (List<ListEntry> subList : getSectionSubLists(mNotifList)) {
+ allSorted &= mSemiStableSort.stabilizeTo(subList, mStableOrder, mNewNotifList);
+ }
+ applyNewNotifList();
+ }
+ assignIndexes(mNotifList);
+ if (!allSorted) {
+ // Report suppressed order changes
+ getStabilityManager().onEntryReorderSuppressed();
+ }
+ }
+
+ private Iterable<List<ListEntry>> getSectionSubLists(List<ListEntry> entries) {
+ return ShadeListBuilderHelper.INSTANCE.getSectionSubLists(entries);
+ }
+
+ private boolean sortGroupChildren(List<NotificationEntry> entries) {
+ if (getStabilityManager().isEveryChangeAllowed()) {
+ entries.sort(mGroupChildrenComparator);
+ return true;
+ } else {
+ return mSemiStableSort.sort(entries, mStableOrder, mGroupChildrenComparator);
+ }
+ }
+
/** Determine whether the items in the list are sorted according to the comparator */
@VisibleForTesting
public static <T> boolean isSorted(List<T> items, Comparator<? super T> comparator) {
@@ -1036,27 +1089,41 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
/**
* Assign the index of each notification relative to the total order
*/
- private static void assignIndexes(List<ListEntry> notifList) {
+ private void assignIndexes(List<ListEntry> notifList) {
if (notifList.size() == 0) return;
NotifSection currentSection = requireNonNull(notifList.get(0).getSection());
int sectionMemberIndex = 0;
for (int i = 0; i < notifList.size(); i++) {
- ListEntry entry = notifList.get(i);
+ final ListEntry entry = notifList.get(i);
NotifSection section = requireNonNull(entry.getSection());
if (section.getIndex() != currentSection.getIndex()) {
sectionMemberIndex = 0;
currentSection = section;
}
- entry.getAttachState().setStableIndex(sectionMemberIndex);
- if (entry instanceof GroupEntry) {
- GroupEntry parent = (GroupEntry) entry;
- for (int j = 0; j < parent.getChildren().size(); j++) {
- entry = parent.getChildren().get(j);
- entry.getAttachState().setStableIndex(sectionMemberIndex);
- sectionMemberIndex++;
+ if (mFlags.isStabilityIndexFixEnabled()) {
+ entry.getAttachState().setStableIndex(sectionMemberIndex++);
+ if (entry instanceof GroupEntry) {
+ final GroupEntry parent = (GroupEntry) entry;
+ final NotificationEntry summary = parent.getSummary();
+ if (summary != null) {
+ summary.getAttachState().setStableIndex(sectionMemberIndex++);
+ }
+ for (NotificationEntry child : parent.getChildren()) {
+ child.getAttachState().setStableIndex(sectionMemberIndex++);
+ }
+ }
+ } else {
+ // This old implementation uses the same index number for the group as the first
+ // child, and fails to assign an index to the summary. Remove once tested.
+ entry.getAttachState().setStableIndex(sectionMemberIndex);
+ if (entry instanceof GroupEntry) {
+ final GroupEntry parent = (GroupEntry) entry;
+ for (NotificationEntry child : parent.getChildren()) {
+ child.getAttachState().setStableIndex(sectionMemberIndex++);
+ }
}
+ sectionMemberIndex++;
}
- sectionMemberIndex++;
}
}
@@ -1196,7 +1263,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
o2.getSectionIndex());
if (cmp != 0) return cmp;
- cmp = Integer.compare(
+ cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare(
getStableOrderIndex(o1),
getStableOrderIndex(o2));
if (cmp != 0) return cmp;
@@ -1225,7 +1292,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
- int cmp = Integer.compare(
+ int cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare(
getStableOrderIndex(o1),
getStableOrderIndex(o2));
if (cmp != 0) return cmp;
@@ -1256,9 +1323,25 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
// let the stability manager constrain or allow reordering
return -1;
}
+ // NOTE(b/241229236): Can't use cleared section index until we fix stability fragility
return entry.getPreviousAttachState().getStableIndex();
}
+ @Nullable
+ private Integer getStableOrderRank(ListEntry entry) {
+ if (getStabilityManager().isEntryReorderingAllowed(entry)) {
+ // let the stability manager constrain or allow reordering
+ return null;
+ }
+ if (entry.getAttachState().getSectionIndex()
+ != entry.getPreviousAttachState().getSectionIndex()) {
+ // stable index is only valid within the same section; otherwise we allow reordering
+ return null;
+ }
+ final int stableIndex = entry.getPreviousAttachState().getStableIndex();
+ return stableIndex == -1 ? null : stableIndex;
+ }
+
private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
final NotifFilter filter = findRejectingFilter(entry, now, filters);
entry.getAttachState().setExcludingFilter(filter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
index 211e37473a70..68d1319699d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.collection.coalescer
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
class GroupCoalescerLogger @Inject constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
index e8f352f60da0..2919def16304 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.row.NotificationGuts
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 8f3eb4f7e223..8a31ed9271ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification
import android.app.Notification.GROUP_ALERT_SUMMARY
import android.util.ArrayMap
+import android.util.ArraySet
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
@@ -70,6 +72,7 @@ class HeadsUpCoordinator @Inject constructor(
@Main private val mExecutor: DelayableExecutor,
) : Coordinator {
private val mEntriesBindingUntil = ArrayMap<String, Long>()
+ private val mEntriesUpdateTimes = ArrayMap<String, Long>()
private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null
private lateinit var mNotifPipeline: NotifPipeline
private var mNow: Long = -1
@@ -264,6 +267,9 @@ class HeadsUpCoordinator @Inject constructor(
}
// After this method runs, all posted entries should have been handled (or skipped).
mPostedEntries.clear()
+
+ // Also take this opportunity to clean up any stale entry update times
+ cleanUpEntryUpdateTimes()
}
/**
@@ -378,6 +384,9 @@ class HeadsUpCoordinator @Inject constructor(
isAlerting = false,
isBinding = false,
)
+
+ // Record the last updated time for this key
+ setUpdateTime(entry, mSystemClock.currentTimeMillis())
}
/**
@@ -419,6 +428,9 @@ class HeadsUpCoordinator @Inject constructor(
cancelHeadsUpBind(posted.entry)
}
}
+
+ // Update last updated time for this entry
+ setUpdateTime(entry, mSystemClock.currentTimeMillis())
}
/**
@@ -426,6 +438,7 @@ class HeadsUpCoordinator @Inject constructor(
*/
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
mPostedEntries.remove(entry.key)
+ mEntriesUpdateTimes.remove(entry.key)
cancelHeadsUpBind(entry)
val entryKey = entry.key
if (mHeadsUpManager.isAlerting(entryKey)) {
@@ -454,7 +467,12 @@ class HeadsUpCoordinator @Inject constructor(
// never) in mPostedEntries to need to alert, we need to check every notification
// known to the pipeline.
for (entry in mNotifPipeline.allNotifs) {
- // The only entries we can consider alerting for here are entries that have never
+ // Only consider entries that are recent enough, since we want to apply a fairly
+ // strict threshold for when an entry should be updated via only ranking and not an
+ // app-provided notification update.
+ if (!isNewEnoughForRankingUpdate(entry)) continue
+
+ // The only entries we consider alerting for here are entries that have never
// interrupted and that now say they should heads up; if they've alerted in the
// past, we don't want to incorrectly alert a second time if there wasn't an
// explicit notification update.
@@ -486,6 +504,41 @@ class HeadsUpCoordinator @Inject constructor(
(entry.sbn.notification.flags and Notification.FLAG_ONLY_ALERT_ONCE) == 0)
}
+ /**
+ * Sets the updated time for the given entry to the specified time.
+ */
+ @VisibleForTesting
+ fun setUpdateTime(entry: NotificationEntry, time: Long) {
+ mEntriesUpdateTimes[entry.key] = time
+ }
+
+ /**
+ * Checks whether the entry is new enough to be updated via ranking update.
+ * We want to avoid updating an entry too long after it was originally posted/updated when we're
+ * only reacting to a ranking change, as relevant ranking updates are expected to come in
+ * fairly soon after the posting of a notification.
+ */
+ private fun isNewEnoughForRankingUpdate(entry: NotificationEntry): Boolean {
+ // If we don't have an update time for this key, default to "too old"
+ if (!mEntriesUpdateTimes.containsKey(entry.key)) return false
+
+ val updateTime = mEntriesUpdateTimes[entry.key] ?: return false
+ return (mSystemClock.currentTimeMillis() - updateTime) <= MAX_RANKING_UPDATE_DELAY_MS
+ }
+
+ private fun cleanUpEntryUpdateTimes() {
+ // Because we won't update entries that are older than this amount of time anyway, clean
+ // up any entries that are too old to notify.
+ val toRemove = ArraySet<String>()
+ for ((key, updateTime) in mEntriesUpdateTimes) {
+ if (updateTime == null ||
+ (mSystemClock.currentTimeMillis() - updateTime) > MAX_RANKING_UPDATE_DELAY_MS) {
+ toRemove.add(key)
+ }
+ }
+ mEntriesUpdateTimes.removeAll(toRemove)
+ }
+
/** When an action is pressed on a notification, end HeadsUp lifetime extension. */
private val mActionPressListener = Consumer<NotificationEntry> { entry ->
if (mNotifsExtendingLifetime.contains(entry)) {
@@ -597,6 +650,9 @@ class HeadsUpCoordinator @Inject constructor(
companion object {
private const val TAG = "HeadsUpCoordinator"
private const val BIND_TIMEOUT = 1000L
+
+ // This value is set to match MAX_SOUND_DELAY_MS in NotificationRecord.
+ private const val MAX_RANKING_UPDATE_DELAY_MS: Long = 2000
}
data class PostedEntry(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index 8625cdbc89d5..dfaa291c6bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -1,9 +1,10 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.util.Log
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
private const val TAG = "HeadsUpCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
index 2480ff65d8fc..0be4bde749f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -16,14 +16,14 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import static com.android.systemui.media.MediaDataManagerKt.isMediaNotification;
+import static com.android.systemui.media.controls.pipeline.MediaDataManagerKt.isMediaNotification;
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 6e76691ae1b1..d2db6224ef52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -407,7 +407,10 @@ public class PreparationCoordinator implements Coordinator {
mLogger.logGroupInflationTookTooLong(group);
return false;
}
- if (mInflatingNotifs.contains(group.getSummary())) {
+ // Only delay release if the summary is not inflated.
+ // TODO(253454977): Once we ensure that all other pipeline filtering and pruning has been
+ // done by this point, we can revert back to checking for mInflatingNotifs.contains(...)
+ if (group.getSummary() != null && !isInflated(group.getSummary())) {
mLogger.logDelayingGroupRelease(group, group.getSummary());
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index c4f4ed54e2fa..9558f47af795 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
index c687e1bacbc9..d80445491bda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
private const val TAG = "ShadeEventCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt
new file mode 100644
index 000000000000..9ec8e07e73b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSort.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.listbuilder
+
+import androidx.annotation.VisibleForTesting
+import kotlin.math.sign
+
+class SemiStableSort {
+ val preallocatedWorkspace by lazy { ArrayList<Any>() }
+ val preallocatedAdditions by lazy { ArrayList<Any>() }
+ val preallocatedMapToIndex by lazy { HashMap<Any, Int>() }
+ val preallocatedMapToIndexComparator: Comparator<Any> by lazy {
+ Comparator.comparingInt { item -> preallocatedMapToIndex[item] ?: -1 }
+ }
+
+ /**
+ * Sort the given [items] such that items which have a [stableOrder] will all be in that order,
+ * items without a [stableOrder] will be sorted according to the comparator, and the two sets of
+ * items will be combined to have the fewest elements out of order according to the [comparator]
+ * . The result will be placed into the original [items] list.
+ */
+ fun <T : Any> sort(
+ items: MutableList<T>,
+ stableOrder: StableOrder<in T>,
+ comparator: Comparator<in T>,
+ ): Boolean =
+ withWorkspace<T, Boolean> { workspace ->
+ val ordered =
+ sortTo(
+ items,
+ stableOrder,
+ comparator,
+ workspace,
+ )
+ items.clear()
+ items.addAll(workspace)
+ return ordered
+ }
+
+ /**
+ * Sort the given [items] such that items which have a [stableOrder] will all be in that order,
+ * items without a [stableOrder] will be sorted according to the comparator, and the two sets of
+ * items will be combined to have the fewest elements out of order according to the [comparator]
+ * . The result will be put into [output].
+ */
+ fun <T : Any> sortTo(
+ items: Iterable<T>,
+ stableOrder: StableOrder<in T>,
+ comparator: Comparator<in T>,
+ output: MutableList<T>,
+ ): Boolean {
+ if (DEBUG) println("\n> START from ${items.map { it to stableOrder.getRank(it) }}")
+ // If array already has elements, use subList to ensure we only append
+ val result = output.takeIf { it.isEmpty() } ?: output.subList(output.size, output.size)
+ items.filterTo(result) { stableOrder.getRank(it) != null }
+ result.sortBy { stableOrder.getRank(it)!! }
+ val isOrdered = result.isSorted(comparator)
+ withAdditions<T> { additions ->
+ items.filterTo(additions) { stableOrder.getRank(it) == null }
+ additions.sortWith(comparator)
+ insertPreSortedElementsWithFewestMisOrderings(result, additions, comparator)
+ }
+ return isOrdered
+ }
+
+ /**
+ * Rearrange the [sortedItems] to enforce that items are in the [stableOrder], and store the
+ * result in [output]. Items with a [stableOrder] will be in that order, items without a
+ * [stableOrder] will remain in same relative order as the input, and the two sets of items will
+ * be combined to have the fewest elements moved from their locations in the original.
+ */
+ fun <T : Any> stabilizeTo(
+ sortedItems: Iterable<T>,
+ stableOrder: StableOrder<in T>,
+ output: MutableList<T>,
+ ): Boolean {
+ // Append to the output array if present
+ val result = output.takeIf { it.isEmpty() } ?: output.subList(output.size, output.size)
+ sortedItems.filterTo(result) { stableOrder.getRank(it) != null }
+ val stableRankComparator = compareBy<T> { stableOrder.getRank(it)!! }
+ val isOrdered = result.isSorted(stableRankComparator)
+ if (!isOrdered) {
+ result.sortWith(stableRankComparator)
+ }
+ if (result.isEmpty()) {
+ sortedItems.filterTo(result) { stableOrder.getRank(it) == null }
+ return isOrdered
+ }
+ withAdditions<T> { additions ->
+ sortedItems.filterTo(additions) { stableOrder.getRank(it) == null }
+ if (additions.isNotEmpty()) {
+ withIndexOfComparator(sortedItems) { comparator ->
+ insertPreSortedElementsWithFewestMisOrderings(result, additions, comparator)
+ }
+ }
+ }
+ return isOrdered
+ }
+
+ private inline fun <T : Any, R> withWorkspace(block: (ArrayList<T>) -> R): R {
+ preallocatedWorkspace.clear()
+ val result = block(preallocatedWorkspace as ArrayList<T>)
+ preallocatedWorkspace.clear()
+ return result
+ }
+
+ private inline fun <T : Any> withAdditions(block: (ArrayList<T>) -> Unit) {
+ preallocatedAdditions.clear()
+ block(preallocatedAdditions as ArrayList<T>)
+ preallocatedAdditions.clear()
+ }
+
+ private inline fun <T : Any> withIndexOfComparator(
+ sortedItems: Iterable<T>,
+ block: (Comparator<in T>) -> Unit
+ ) {
+ preallocatedMapToIndex.clear()
+ sortedItems.forEachIndexed { i, item -> preallocatedMapToIndex[item] = i }
+ block(preallocatedMapToIndexComparator as Comparator<in T>)
+ preallocatedMapToIndex.clear()
+ }
+
+ companion object {
+
+ /**
+ * This is the core of the algorithm.
+ *
+ * Insert [preSortedAdditions] (the elements to be inserted) into [existing] without
+ * changing the relative order of any elements already in [existing], even though those
+ * elements may be mis-ordered relative to the [comparator], such that the total number of
+ * elements which are ordered incorrectly according to the [comparator] is fewest.
+ */
+ private fun <T> insertPreSortedElementsWithFewestMisOrderings(
+ existing: MutableList<T>,
+ preSortedAdditions: Iterable<T>,
+ comparator: Comparator<in T>,
+ ) {
+ if (DEBUG) println(" To $existing insert $preSortedAdditions with fewest misordering")
+ var iStart = 0
+ preSortedAdditions.forEach { toAdd ->
+ if (DEBUG) println(" need to add $toAdd to $existing, starting at $iStart")
+ var cmpSum = 0
+ var cmpSumMax = 0
+ var iCmpSumMax = iStart
+ if (DEBUG) print(" ")
+ for (i in iCmpSumMax until existing.size) {
+ val cmp = comparator.compare(toAdd, existing[i]).sign
+ cmpSum += cmp
+ if (cmpSum > cmpSumMax) {
+ cmpSumMax = cmpSum
+ iCmpSumMax = i + 1
+ }
+ if (DEBUG) print("sum[$i]=$cmpSum, ")
+ }
+ if (DEBUG) println("inserting $toAdd at $iCmpSumMax")
+ existing.add(iCmpSumMax, toAdd)
+ iStart = iCmpSumMax + 1
+ }
+ }
+
+ /** Determines if a list is correctly sorted according to the given comparator */
+ @VisibleForTesting
+ fun <T> List<T>.isSorted(comparator: Comparator<T>): Boolean {
+ if (this.size <= 1) {
+ return true
+ }
+ val iterator = this.iterator()
+ var previous = iterator.next()
+ var current: T?
+ while (iterator.hasNext()) {
+ current = iterator.next()
+ if (comparator.compare(previous, current) > 0) {
+ return false
+ }
+ previous = current
+ }
+ return true
+ }
+ }
+
+ fun interface StableOrder<T> {
+ fun getRank(item: T): Int?
+ }
+}
+
+val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt
new file mode 100644
index 000000000000..d8f75f61c05a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelper.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.listbuilder
+
+import com.android.systemui.statusbar.notification.collection.ListEntry
+
+object ShadeListBuilderHelper {
+ fun getSectionSubLists(entries: List<ListEntry>): Iterable<List<ListEntry>> =
+ getContiguousSubLists(entries, minLength = 1) { it.sectionIndex }
+
+ inline fun <T : Any, K : Any> getContiguousSubLists(
+ itemList: List<T>,
+ minLength: Int = 1,
+ key: (T) -> K,
+ ): Iterable<List<T>> {
+ val subLists = mutableListOf<List<T>>()
+ val numEntries = itemList.size
+ var currentSectionStartIndex = 0
+ var currentSectionKey: K? = null
+ for (i in 0 until numEntries) {
+ val sectionKey = key(itemList[i])
+ if (currentSectionKey == null) {
+ currentSectionKey = sectionKey
+ } else if (currentSectionKey != sectionKey) {
+ val length = i - currentSectionStartIndex
+ if (length >= minLength) {
+ subLists.add(itemList.subList(currentSectionStartIndex, i))
+ }
+ currentSectionStartIndex = i
+ currentSectionKey = sectionKey
+ }
+ }
+ val length = numEntries - currentSectionStartIndex
+ if (length >= minLength) {
+ subLists.add(itemList.subList(currentSectionStartIndex, numEntries))
+ }
+ return subLists
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index d8dae5d23f42..8e052c7dcc5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -16,11 +16,11 @@
package com.android.systemui.statusbar.notification.collection.listbuilder
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.WARNING
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index aa27e1e407f0..911a2d0c2b36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -20,13 +20,13 @@ import android.os.RemoteException
import android.service.notification.NotificationListenerService
import android.service.notification.NotificationListenerService.RankingMap
import android.service.notification.StatusBarNotification
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
-import com.android.systemui.log.LogLevel.WTF
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.plugins.log.LogLevel.WTF
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason
import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
index 38e3d496a60c..9c71e5c1054c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.collection.render
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.util.Compile
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
index b6278d1d5f01..fde4ecb7bcaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.render
+import javax.inject.Inject
+
/** An interface by which the pipeline can make updates to the notification root view. */
interface NotifStackController {
/** Provides stats about the list of notifications attached to the shade */
@@ -42,6 +44,6 @@ data class NotifStats(
* methods, rather than forcing us to add no-op implementations in their implementation every time
* a method is added.
*/
-open class DefaultNotifStackController : NotifStackController {
+open class DefaultNotifStackController @Inject constructor() : NotifStackController {
override fun setNotifStats(stats: NotifStats) {}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index 6d1071c283e3..b4b9438cd6be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.collection.render
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import java.lang.RuntimeException
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
index b2cb23bd11aa..a5278c3d0ad3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
@@ -23,6 +23,7 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.CentralSurfaces
/**
* The master controller for all notifications-related work
@@ -32,6 +33,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain
*/
interface NotificationsController {
fun initialize(
+ centralSurfaces: CentralSurfaces,
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
stackController: NotifStackController,
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 8e646a37a4b3..8eef3f36433d 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
@@ -24,6 +24,7 @@ import com.android.systemui.flags.Flags
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationListener
+import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.AnimatedImageNotificationManager
import com.android.systemui.statusbar.notification.NotificationActivityStarter
@@ -38,6 +39,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
+import com.android.systemui.statusbar.notification.logging.NotificationLogger
import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor
import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -56,7 +58,6 @@ import javax.inject.Inject
*/
@SysUISingleton
class NotificationsControllerImpl @Inject constructor(
- private val centralSurfaces: Lazy<CentralSurfaces>,
private val notificationListener: NotificationListener,
private val commonNotifCollection: Lazy<CommonNotifCollection>,
private val notifPipeline: Lazy<NotifPipeline>,
@@ -64,7 +65,9 @@ class NotificationsControllerImpl @Inject constructor(
private val targetSdkResolver: TargetSdkResolver,
private val notifPipelineInitializer: Lazy<NotifPipelineInitializer>,
private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
+ private val notificationLogger: NotificationLogger,
private val notificationRowBinder: NotificationRowBinderImpl,
+ private val notificationsMediaManager: NotificationMediaManager,
private val headsUpViewBinder: HeadsUpViewBinder,
private val clickerBuilder: NotificationClicker.Builder,
private val animatedImageNotificationManager: AnimatedImageNotificationManager,
@@ -76,6 +79,7 @@ class NotificationsControllerImpl @Inject constructor(
) : NotificationsController {
override fun initialize(
+ centralSurfaces: CentralSurfaces,
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
stackController: NotifStackController,
@@ -92,8 +96,8 @@ class NotificationsControllerImpl @Inject constructor(
notificationRowBinder.setNotificationClicker(
clickerBuilder.build(
- Optional.of(
- centralSurfaces.get()), bubblesOptional, notificationActivityStarter))
+ Optional.ofNullable(centralSurfaces), bubblesOptional,
+ notificationActivityStarter))
notificationRowBinder.setUpWithPresenter(
presenter,
listContainer,
@@ -109,7 +113,8 @@ class NotificationsControllerImpl @Inject constructor(
stackController)
targetSdkResolver.initialize(notifPipeline.get())
-
+ notificationsMediaManager.setUpWithPresenter(presenter)
+ notificationLogger.setUpWithContainer(listContainer)
peopleSpaceWidgetManager.attach(notificationListener)
fgsNotifListener.init()
if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_MONITOR_ENABLED)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
index 744166d87907..14856dafdb11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
@@ -24,6 +24,7 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.CentralSurfaces
import javax.inject.Inject
/**
@@ -34,6 +35,7 @@ class NotificationsControllerStub @Inject constructor(
) : NotificationsController {
override fun initialize(
+ centralSurfaces: CentralSurfaces,
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
stackController: NotifStackController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
index 5dbec8dcba20..d4f11fc141f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.statusbar.notification.interruption
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 659df24aad2a..e6dbcee10f60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -231,7 +231,7 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
private fun readShowSilentNotificationSetting() {
val showSilentNotifs =
secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
- true, UserHandle.USER_CURRENT)
+ false, UserHandle.USER_CURRENT)
hideSilentNotificationsOnLockscreen = !showSilentNotifs
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 99d320d1c7ca..073b6b041b81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -16,11 +16,11 @@
package com.android.systemui.statusbar.notification.interruption
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotificationInterruptLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.WARNING
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index c5a69217a1ac..c4f5a3a30608 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.interruption;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD;
+import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR;
import android.app.NotificationManager;
import android.content.ContentResolver;
@@ -32,6 +34,8 @@ import android.service.notification.StatusBarNotification;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -68,10 +72,30 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
private final NotificationInterruptLogger mLogger;
private final NotifPipelineFlags mFlags;
private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
+ private final UiEventLogger mUiEventLogger;
@VisibleForTesting
protected boolean mUseHeadsUp = false;
+ public enum NotificationInterruptEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "FSI suppressed for suppressive GroupAlertBehavior")
+ FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(1235),
+
+ @UiEvent(doc = "FSI suppressed for requiring neither HUN nor keyguard")
+ FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236);
+
+ private final int mId;
+
+ NotificationInterruptEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
@Inject
public NotificationInterruptStateProviderImpl(
ContentResolver contentResolver,
@@ -85,7 +109,8 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
NotificationInterruptLogger logger,
@Main Handler mainHandler,
NotifPipelineFlags flags,
- KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
+ KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
+ UiEventLogger uiEventLogger) {
mContentResolver = contentResolver;
mPowerManager = powerManager;
mDreamManager = dreamManager;
@@ -97,6 +122,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
mLogger = logger;
mFlags = flags;
mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
+ mUiEventLogger = uiEventLogger;
ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -203,7 +229,9 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
// b/231322873: Detect and report an event when a notification has both an FSI and a
// suppressive groupAlertBehavior, and now correctly block the FSI from firing.
final int uid = entry.getSbn().getUid();
+ final String packageName = entry.getSbn().getPackageName();
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior");
+ mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid, packageName);
mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
return false;
}
@@ -249,7 +277,9 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
// Detect the case determined by b/231322873 to launch FSI while device is in use,
// as blocked by the correct implementation, and report the event.
final int uid = entry.getSbn().getUid();
+ final String packageName = entry.getSbn().getPackageName();
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard");
+ mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName);
mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
index 832a739a9080..0380fff1e2af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
@@ -20,8 +20,9 @@ package com.android.systemui.statusbar.notification.logging
/** Describes usage of a notification. */
data class NotificationMemoryUsage(
val packageName: String,
- val notificationId: String,
+ val notificationKey: String,
val objectUsage: NotificationObjectUsage,
+ val viewUsage: List<NotificationViewUsage>
)
/**
@@ -39,3 +40,26 @@ data class NotificationObjectUsage(
val extender: Int,
val hasCustomView: Boolean,
)
+
+enum class ViewType {
+ PUBLIC_VIEW,
+ PRIVATE_CONTRACTED_VIEW,
+ PRIVATE_EXPANDED_VIEW,
+ PRIVATE_HEADS_UP_VIEW,
+ TOTAL
+}
+
+/**
+ * Describes current memory of a notification view hierarchy.
+ *
+ * The values are in bytes.
+ */
+data class NotificationViewUsage(
+ val viewType: ViewType,
+ val smallIcon: Int,
+ val largeIcon: Int,
+ val systemIcons: Int,
+ val style: Int,
+ val customViews: Int,
+ val softwareBitmapsPenalty: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
new file mode 100644
index 000000000000..7d39e18ab349
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
@@ -0,0 +1,212 @@
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.Person
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.annotation.WorkerThread
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/** Calculates estimated memory usage of [Notification] and [NotificationEntry] objects. */
+internal object NotificationMemoryMeter {
+
+ private const val CAR_EXTENSIONS = "android.car.EXTENSIONS"
+ private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon"
+ private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
+ private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
+ private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+
+ /** Returns a list of memory use entries for currently shown notifications. */
+ @WorkerThread
+ fun notificationMemoryUse(
+ notifications: Collection<NotificationEntry>,
+ ): List<NotificationMemoryUsage> {
+ return notifications
+ .asSequence()
+ .map { entry ->
+ val packageName = entry.sbn.packageName
+ val notificationObjectUsage =
+ notificationMemoryUse(entry.sbn.notification, hashSetOf())
+ val notificationViewUsage = NotificationMemoryViewWalker.getViewUsage(entry.row)
+ NotificationMemoryUsage(
+ packageName,
+ NotificationUtils.logKey(entry.sbn.key),
+ notificationObjectUsage,
+ notificationViewUsage
+ )
+ }
+ .toList()
+ }
+
+ @WorkerThread
+ fun notificationMemoryUse(
+ entry: NotificationEntry,
+ seenBitmaps: HashSet<Int> = hashSetOf(),
+ ): NotificationMemoryUsage {
+ return NotificationMemoryUsage(
+ entry.sbn.packageName,
+ NotificationUtils.logKey(entry.sbn.key),
+ notificationMemoryUse(entry.sbn.notification, seenBitmaps),
+ NotificationMemoryViewWalker.getViewUsage(entry.row)
+ )
+ }
+
+ /**
+ * Computes the estimated memory usage of a given [Notification] object. It'll attempt to
+ * inspect Bitmaps in the object and provide summary of memory usage.
+ */
+ @WorkerThread
+ fun notificationMemoryUse(
+ notification: Notification,
+ seenBitmaps: HashSet<Int> = hashSetOf(),
+ ): NotificationObjectUsage {
+ val extras = notification.extras
+ val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps)
+ val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps)
+
+ // Collect memory usage of extra styles
+
+ // Big Picture
+ val bigPictureIconUse =
+ computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps)
+ val bigPictureUse =
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) +
+ computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps)
+
+ // People
+ val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST)
+ val peopleUse =
+ peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0
+
+ // Calling
+ val callingPersonUse =
+ computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps)
+ val verificationIconUse =
+ computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps)
+
+ // Messages
+ val messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ extras.getParcelableArray(Notification.EXTRA_MESSAGES)
+ )
+ val messagesUse =
+ messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+ val historicMessages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES)
+ )
+ val historyicMessagesUse =
+ historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
+
+ // Extenders
+ val carExtender = extras.getBundle(CAR_EXTENSIONS)
+ val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0
+ val carExtenderIcon =
+ computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps)
+
+ val tvExtender = extras.getBundle(TV_EXTENSIONS)
+ val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0
+
+ val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS)
+ val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0
+ val wearExtenderBackground =
+ computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
+
+ val style = notification.notificationStyle
+ val hasCustomView = notification.contentView != null || notification.bigContentView != null
+ val extrasSize = computeBundleSize(extras)
+
+ return NotificationObjectUsage(
+ smallIcon = smallIconUse,
+ largeIcon = largeIconUse,
+ extras = extrasSize,
+ style = style?.simpleName,
+ styleIcon =
+ bigPictureIconUse +
+ peopleUse +
+ callingPersonUse +
+ verificationIconUse +
+ messagesUse +
+ historyicMessagesUse,
+ bigPicture = bigPictureUse,
+ extender =
+ carExtenderSize +
+ carExtenderIcon +
+ tvExtenderSize +
+ wearExtenderSize +
+ wearExtenderBackground,
+ hasCustomView = hasCustomView
+ )
+ }
+
+ /**
+ * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
+ * bitmaps). Can be slow.
+ */
+ private fun computeBundleSize(extras: Bundle): Int {
+ val parcel = Parcel.obtain()
+ try {
+ extras.writeToParcel(parcel, 0)
+ return parcel.dataSize()
+ } finally {
+ parcel.recycle()
+ }
+ }
+
+ /**
+ * Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0
+ * if the key does not exist in extras.
+ */
+ private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int {
+ return when (val parcelable = extras?.getParcelable<Parcelable>(key)) {
+ is Bitmap -> computeBitmapUse(parcelable, seenBitmaps)
+ is Icon -> computeIconUse(parcelable, seenBitmaps)
+ is Person -> computeIconUse(parcelable.icon, seenBitmaps)
+ else -> 0
+ }
+ }
+
+ /**
+ * Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is
+ * defined via Uri or a resource.
+ *
+ * @return memory usage in bytes or 0 if the icon is Uri/Resource based
+ */
+ private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
+ when (icon?.type) {
+ Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+ Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
+ Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps)
+ else -> 0
+ }
+
+ /**
+ * Returns the amount of memory a given bitmap is using. If the bitmap reference is part of
+ * seenBitmaps set, this method returns 0 to avoid double counting.
+ *
+ * @return memory usage of the bitmap in bytes
+ */
+ private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int {
+ val refId = System.identityHashCode(bitmap)
+ if (seenBitmaps?.contains(refId) == true) {
+ return 0
+ }
+
+ seenBitmaps?.add(refId)
+ return bitmap.allocationByteCount
+ }
+
+ private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int {
+ val refId = System.identityHashCode(icon.dataBytes)
+ if (seenBitmaps.contains(refId)) {
+ return 0
+ }
+
+ seenBitmaps.add(refId)
+ return icon.dataLength
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
index 958978ecd858..c09cc4306ced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -17,22 +17,11 @@
package com.android.systemui.statusbar.notification.logging
-import android.app.Notification
-import android.app.Person
-import android.graphics.Bitmap
-import android.graphics.drawable.Icon
-import android.os.Bundle
-import android.os.Parcel
-import android.os.Parcelable
import android.util.Log
-import androidx.annotation.WorkerThread
-import androidx.core.util.contains
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
import java.io.PrintWriter
import javax.inject.Inject
@@ -46,12 +35,7 @@ constructor(
) : Dumpable {
companion object {
- private const val TAG = "NotificationMemMonitor"
- private const val CAR_EXTENSIONS = "android.car.EXTENSIONS"
- private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon"
- private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
- private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
- private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+ private const val TAG = "NotificationMemory"
}
fun init() {
@@ -60,184 +44,123 @@ constructor(
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
- currentNotificationMemoryUse().forEach { use -> pw.println(use.toString()) }
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
+ .sortedWith(compareBy({ it.packageName }, { it.notificationKey }))
+ dumpNotificationObjects(pw, memoryUse)
+ dumpNotificationViewUsage(pw, memoryUse)
}
- @WorkerThread
- fun currentNotificationMemoryUse(): List<NotificationMemoryUsage> {
- return notificationMemoryUse(notificationPipeline.allNotifs)
- }
-
- /** Returns a list of memory use entries for currently shown notifications. */
- @WorkerThread
- fun notificationMemoryUse(
- notifications: Collection<NotificationEntry>
- ): List<NotificationMemoryUsage> {
- return notifications
- .asSequence()
- .map { entry ->
- val packageName = entry.sbn.packageName
- val notificationObjectUsage =
- computeNotificationObjectUse(entry.sbn.notification, hashSetOf())
- NotificationMemoryUsage(
- packageName,
- NotificationUtils.logKey(entry.sbn.key),
- notificationObjectUsage
- )
- }
- .toList()
- }
-
- /**
- * Computes the estimated memory usage of a given [Notification] object. It'll attempt to
- * inspect Bitmaps in the object and provide summary of memory usage.
- */
- private fun computeNotificationObjectUse(
- notification: Notification,
- seenBitmaps: HashSet<Int>
- ): NotificationObjectUsage {
- val extras = notification.extras
- val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps)
- val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps)
-
- // Collect memory usage of extra styles
-
- // Big Picture
- val bigPictureIconUse =
- computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps) +
- computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps)
- val bigPictureUse =
- computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) +
- computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps)
-
- // People
- val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST)
- val peopleUse =
- peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0
-
- // Calling
- val callingPersonUse =
- computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps)
- val verificationIconUse =
- computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps)
-
- // Messages
- val messages =
- Notification.MessagingStyle.Message.getMessagesFromBundleArray(
- extras.getParcelableArray(Notification.EXTRA_MESSAGES)
- )
- val messagesUse =
- messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
- val historicMessages =
- Notification.MessagingStyle.Message.getMessagesFromBundleArray(
- extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES)
+ /** Renders a table of notification object usage into passed [PrintWriter]. */
+ private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
+ pw.println("Notification Object Usage")
+ pw.println("-----------")
+ pw.println(
+ "Package".padEnd(35) +
+ "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
+ )
+ pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
+ pw.println()
+
+ memoryUse.forEach { use ->
+ pw.println(
+ use.packageName.padEnd(35) +
+ "\t\t" +
+ "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
+ (use.objectUsage.style?.take(15) ?: "").padEnd(15) +
+ "\t\t${use.objectUsage.styleIcon}\t" +
+ "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
+ "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
+ use.notificationKey
)
- val historyicMessagesUse =
- historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) }
-
- // Extenders
- val carExtender = extras.getBundle(CAR_EXTENSIONS)
- val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0
- val carExtenderIcon =
- computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps)
-
- val tvExtender = extras.getBundle(TV_EXTENSIONS)
- val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0
-
- val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS)
- val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0
- val wearExtenderBackground =
- computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
-
- val style = notification.notificationStyle
- val hasCustomView = notification.contentView != null || notification.bigContentView != null
- val extrasSize = computeBundleSize(extras)
-
- return NotificationObjectUsage(
- smallIconUse,
- largeIconUse,
- extrasSize,
- style?.simpleName,
- bigPictureIconUse +
- peopleUse +
- callingPersonUse +
- verificationIconUse +
- messagesUse +
- historyicMessagesUse,
- bigPictureUse,
- carExtenderSize +
- carExtenderIcon +
- tvExtenderSize +
- wearExtenderSize +
- wearExtenderBackground,
- hasCustomView
+ }
+
+ // Calculate totals for easily glanceable summary.
+ data class Totals(
+ var smallIcon: Int = 0,
+ var largeIcon: Int = 0,
+ var styleIcon: Int = 0,
+ var bigPicture: Int = 0,
+ var extender: Int = 0,
+ var extras: Int = 0,
)
- }
- /**
- * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
- * bitmaps). Can be slow.
- */
- private fun computeBundleSize(extras: Bundle): Int {
- val parcel = Parcel.obtain()
- try {
- extras.writeToParcel(parcel, 0)
- return parcel.dataSize()
- } finally {
- parcel.recycle()
- }
- }
+ val totals =
+ memoryUse.fold(Totals()) { t, usage ->
+ t.smallIcon += usage.objectUsage.smallIcon
+ t.largeIcon += usage.objectUsage.largeIcon
+ t.styleIcon += usage.objectUsage.styleIcon
+ t.bigPicture += usage.objectUsage.bigPicture
+ t.extender += usage.objectUsage.extender
+ t.extras += usage.objectUsage.extras
+ t
+ }
- /**
- * Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0
- * if the key does not exist in extras.
- */
- private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int {
- return when (val parcelable = extras?.getParcelable<Parcelable>(key)) {
- is Bitmap -> computeBitmapUse(parcelable, seenBitmaps)
- is Icon -> computeIconUse(parcelable, seenBitmaps)
- is Person -> computeIconUse(parcelable.icon, seenBitmaps)
- else -> 0
- }
+ pw.println()
+ pw.println("TOTALS")
+ pw.println(
+ "".padEnd(35) +
+ "\t\t" +
+ "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
+ "".padEnd(15) +
+ "\t\t${toKb(totals.styleIcon)}\t" +
+ "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
+ toKb(totals.extras)
+ )
+ pw.println()
}
- /**
- * Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is
- * defined via Uri or a resource.
- *
- * @return memory usage in bytes or 0 if the icon is Uri/Resource based
- */
- private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
- when (icon?.type) {
- Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
- Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
- Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps)
- else -> 0
- }
-
- /**
- * Returns the amount of memory a given bitmap is using. If the bitmap reference is part of
- * seenBitmaps set, this method returns 0 to avoid double counting.
- *
- * @return memory usage of the bitmap in bytes
- */
- private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int {
- val refId = System.identityHashCode(bitmap)
- if (seenBitmaps?.contains(refId) == true) {
- return 0
- }
+ /** Renders a table of notification view usage into passed [PrintWriter] */
+ private fun dumpNotificationViewUsage(
+ pw: PrintWriter,
+ memoryUse: List<NotificationMemoryUsage>,
+ ) {
+
+ data class Totals(
+ var smallIcon: Int = 0,
+ var largeIcon: Int = 0,
+ var style: Int = 0,
+ var customViews: Int = 0,
+ var softwareBitmapsPenalty: Int = 0,
+ )
- seenBitmaps?.add(refId)
- return bitmap.allocationByteCount
+ val totals = Totals()
+ pw.println("Notification View Usage")
+ pw.println("-----------")
+ pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
+ pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
+ pw.println()
+ memoryUse
+ .filter { it.viewUsage.isNotEmpty() }
+ .forEach { use ->
+ pw.println(use.packageName + " " + use.notificationKey)
+ use.viewUsage.forEach { view ->
+ pw.println(
+ " ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
+ "\t${view.largeIcon}\t${view.style}" +
+ "\t${view.customViews}\t${view.softwareBitmapsPenalty}"
+ )
+
+ if (view.viewType == ViewType.TOTAL) {
+ totals.smallIcon += view.smallIcon
+ totals.largeIcon += view.largeIcon
+ totals.style += view.style
+ totals.customViews += view.customViews
+ totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
+ }
+ }
+ }
+ pw.println()
+ pw.println("TOTALS")
+ pw.println(
+ " ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
+ "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
+ "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
+ )
+ pw.println()
}
- private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int {
- val refId = System.identityHashCode(icon.dataBytes)
- if (seenBitmaps.contains(refId)) {
- return 0
- }
-
- seenBitmaps.add(refId)
- return icon.dataLength
+ private fun toKb(bytes: Int): String {
+ return (bytes / 1024).toString() + " KB"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
new file mode 100644
index 000000000000..a0bee1502f51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
@@ -0,0 +1,173 @@
+package com.android.systemui.statusbar.notification.logging
+
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import com.android.internal.R
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.util.children
+
+/** Walks view hiearchy of a given notification to estimate its memory use. */
+internal object NotificationMemoryViewWalker {
+
+ private const val TAG = "NotificationMemory"
+
+ /** Builder for [NotificationViewUsage] objects. */
+ private class UsageBuilder {
+ private var smallIcon: Int = 0
+ private var largeIcon: Int = 0
+ private var systemIcons: Int = 0
+ private var style: Int = 0
+ private var customViews: Int = 0
+ private var softwareBitmaps = 0
+
+ fun addSmallIcon(smallIconUse: Int) = apply { smallIcon += smallIconUse }
+ fun addLargeIcon(largeIconUse: Int) = apply { largeIcon += largeIconUse }
+ fun addSystem(systemIconUse: Int) = apply { systemIcons += systemIconUse }
+ fun addStyle(styleUse: Int) = apply { style += styleUse }
+ fun addSoftwareBitmapPenalty(softwareBitmapUse: Int) = apply {
+ softwareBitmaps += softwareBitmapUse
+ }
+
+ fun addCustomViews(customViewsUse: Int) = apply { customViews += customViewsUse }
+
+ fun build(viewType: ViewType): NotificationViewUsage {
+ return NotificationViewUsage(
+ viewType = viewType,
+ smallIcon = smallIcon,
+ largeIcon = largeIcon,
+ systemIcons = systemIcons,
+ style = style,
+ customViews = customViews,
+ softwareBitmapsPenalty = softwareBitmaps,
+ )
+ }
+ }
+
+ /**
+ * Returns memory usage of public and private views contained in passed
+ * [ExpandableNotificationRow]
+ */
+ fun getViewUsage(row: ExpandableNotificationRow?): List<NotificationViewUsage> {
+ if (row == null) {
+ return listOf()
+ }
+
+ // The ordering here is significant since it determines deduplication of seen drawables.
+ return listOf(
+ getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
+ getViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, row.privateLayout?.contractedChild),
+ getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
+ getViewUsage(ViewType.PUBLIC_VIEW, row.publicLayout),
+ getTotalUsage(row)
+ )
+ }
+
+ /**
+ * Calculate total usage of all views - we need to do a separate traversal to make sure we don't
+ * double count fields.
+ */
+ private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage {
+ val totalUsage = UsageBuilder()
+ val seenObjects = hashSetOf<Int>()
+
+ row.publicLayout?.let { computeViewHierarchyUse(it, totalUsage, seenObjects) }
+ row.privateLayout?.let { child ->
+ for (view in listOf(child.expandedChild, child.contractedChild, child.headsUpChild)) {
+ (view as? ViewGroup)?.let { v ->
+ computeViewHierarchyUse(v, totalUsage, seenObjects)
+ }
+ }
+ }
+ return totalUsage.build(ViewType.TOTAL)
+ }
+
+ private fun getViewUsage(
+ type: ViewType,
+ rootView: View?,
+ seenObjects: HashSet<Int> = hashSetOf()
+ ): NotificationViewUsage {
+ val usageBuilder = UsageBuilder()
+ (rootView as? ViewGroup)?.let { computeViewHierarchyUse(it, usageBuilder, seenObjects) }
+ return usageBuilder.build(type)
+ }
+
+ private fun computeViewHierarchyUse(
+ rootView: ViewGroup,
+ builder: UsageBuilder,
+ seenObjects: HashSet<Int> = hashSetOf(),
+ ) {
+ for (child in rootView.children) {
+ if (child is ViewGroup) {
+ computeViewHierarchyUse(child, builder, seenObjects)
+ } else {
+ computeViewUse(child, builder, seenObjects)
+ }
+ }
+ }
+
+ private fun computeViewUse(view: View, builder: UsageBuilder, seenObjects: HashSet<Int>) {
+ if (view !is ImageView) return
+ val drawable = view.drawable ?: return
+ val drawableRef = System.identityHashCode(drawable)
+ if (seenObjects.contains(drawableRef)) return
+ val drawableUse = computeDrawableUse(drawable, seenObjects)
+ // TODO(b/235451049): We need to make sure we traverse large icon before small icon -
+ // sometimes the large icons are assigned to small icon views and we want to
+ // attribute them to large view in those cases.
+ when (view.id) {
+ R.id.left_icon,
+ R.id.icon,
+ R.id.conversation_icon -> builder.addSmallIcon(drawableUse)
+ R.id.right_icon -> builder.addLargeIcon(drawableUse)
+ R.id.big_picture -> builder.addStyle(drawableUse)
+ // Elements that are part of platform with resources
+ R.id.phishing_alert,
+ R.id.feedback,
+ R.id.alerted_icon,
+ R.id.expand_button_icon,
+ R.id.remote_input_send -> builder.addSystem(drawableUse)
+ // Custom view ImageViews
+ else -> {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Custom view: ${identifierForView(view)}")
+ }
+ builder.addCustomViews(drawableUse)
+ }
+ }
+
+ if (isDrawableSoftwareBitmap(drawable)) {
+ builder.addSoftwareBitmapPenalty(drawableUse)
+ }
+
+ seenObjects.add(drawableRef)
+ }
+
+ private fun computeDrawableUse(drawable: Drawable, seenObjects: HashSet<Int>): Int =
+ when (drawable) {
+ is BitmapDrawable -> {
+ val ref = System.identityHashCode(drawable.bitmap)
+ if (seenObjects.contains(ref)) {
+ 0
+ } else {
+ seenObjects.add(ref)
+ drawable.bitmap.allocationByteCount
+ }
+ }
+ else -> 0
+ }
+
+ private fun isDrawableSoftwareBitmap(drawable: Drawable) =
+ drawable is BitmapDrawable && drawable.bitmap.config != Bitmap.Config.HARDWARE
+
+ private fun identifierForView(view: View) =
+ if (view.id == View.NO_ID) {
+ "no-id"
+ } else {
+ view.resources.getResourceName(view.id)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
index fe03b2ad6a32..10197a38527e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.logging
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationRenderLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.NotificationSection
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 755e3e1a208e..d29298a2f637 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -613,22 +613,21 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
protected void resetAllContentAlphas() {}
@Override
- protected void applyRoundness() {
+ public void applyRoundness() {
super.applyRoundness();
- applyBackgroundRoundness(getCurrentBackgroundRadiusTop(),
- getCurrentBackgroundRadiusBottom());
+ applyBackgroundRoundness(getTopCornerRadius(), getBottomCornerRadius());
}
@Override
- public float getCurrentBackgroundRadiusTop() {
+ public float getTopCornerRadius() {
float fraction = getInterpolatedAppearAnimationFraction();
- return MathUtils.lerp(0, super.getCurrentBackgroundRadiusTop(), fraction);
+ return MathUtils.lerp(0, super.getTopCornerRadius(), fraction);
}
@Override
- public float getCurrentBackgroundRadiusBottom() {
+ public float getBottomCornerRadius() {
float fraction = getInterpolatedAppearAnimationFraction();
- return MathUtils.lerp(0, super.getCurrentBackgroundRadiusBottom(), fraction);
+ return MathUtils.lerp(0, super.getBottomCornerRadius(), fraction);
}
private void applyBackgroundRoundness(float topRadius, float bottomRadius) {
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 1b006485c83d..9e7717caf69c 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
@@ -93,6 +93,7 @@ import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -154,7 +155,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
void onLayout();
}
- /** Listens for changes to the expansion state of this row. */
+ /**
+ * Listens for changes to the expansion state of this row.
+ */
public interface OnExpansionChangedListener {
void onExpansionChanged(boolean isExpanded);
}
@@ -183,22 +186,34 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private int mNotificationLaunchHeight;
private boolean mMustStayOnScreen;
- /** Does this row contain layouts that can adapt to row expansion */
+ /**
+ * Does this row contain layouts that can adapt to row expansion
+ */
private boolean mExpandable;
- /** Has the user actively changed the expansion state of this row */
+ /**
+ * Has the user actively changed the expansion state of this row
+ */
private boolean mHasUserChangedExpansion;
- /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
+ /**
+ * If {@link #mHasUserChangedExpansion}, has the user expanded this row
+ */
private boolean mUserExpanded;
- /** Whether the blocking helper is showing on this notification (even if dismissed) */
+ /**
+ * Whether the blocking helper is showing on this notification (even if dismissed)
+ */
private boolean mIsBlockingHelperShowing;
/**
* Has this notification been expanded while it was pinned
*/
private boolean mExpandedWhenPinned;
- /** Is the user touching this row */
+ /**
+ * Is the user touching this row
+ */
private boolean mUserLocked;
- /** Are we showing the "public" version */
+ /**
+ * Are we showing the "public" version
+ */
private boolean mShowingPublic;
private boolean mSensitive;
private boolean mSensitiveHiddenInGeneral;
@@ -351,11 +366,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private boolean mWasChildInGroupWhenRemoved;
private NotificationInlineImageResolver mImageResolver;
private NotificationMediaManager mMediaManager;
- @Nullable private OnExpansionChangedListener mExpansionChangedListener;
- @Nullable private Runnable mOnIntrinsicHeightReachedRunnable;
+ @Nullable
+ private OnExpansionChangedListener mExpansionChangedListener;
+ @Nullable
+ private Runnable mOnIntrinsicHeightReachedRunnable;
private float mTopRoundnessDuringLaunchAnimation;
private float mBottomRoundnessDuringLaunchAnimation;
+ private boolean mIsNotificationGroupCornerEnabled;
/**
* Returns whether the given {@code statusBarNotification} is a system notification.
@@ -574,14 +592,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
- /** Called when the notification's ranking was changed (but nothing else changed). */
+ /**
+ * Called when the notification's ranking was changed (but nothing else changed).
+ */
public void onNotificationRankingUpdated() {
if (mMenuRow != null) {
mMenuRow.onNotificationUpdated(mEntry.getSbn());
}
}
- /** Call when bubble state has changed and the button on the notification should be updated. */
+ /**
+ * Call when bubble state has changed and the button on the notification should be updated.
+ */
public void updateBubbleButton() {
for (NotificationContentView l : mLayouts) {
l.updateBubbleButton(mEntry);
@@ -620,6 +642,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/**
* Sets a supplier that can determine whether the keyguard is secure or not.
+ *
* @param secureStateProvider A function that returns true if keyguard is secure.
*/
public void setSecureStateProvider(BooleanSupplier secureStateProvider) {
@@ -781,7 +804,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.setUntruncatedChildCount(childCount);
}
- /** Called after children have been attached to set the expansion states */
+ /**
+ * Called after children have been attached to set the expansion states
+ */
public void resetChildSystemExpandedStates() {
if (isSummaryWithChildren()) {
mChildrenContainer.updateExpansionStates();
@@ -791,7 +816,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/**
* Add a child notification to this view.
*
- * @param row the row to add
+ * @param row the row to add
* @param childIndex the index to add it at, if -1 it will be added at the end
*/
public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
@@ -809,10 +834,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
onAttachedChildrenCountChanged();
row.setIsChildInGroup(false, null);
- row.setBottomRoundness(0.0f, false /* animate */);
+ row.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
}
- /** Returns the child notification at [index], or null if no such child. */
+ /**
+ * Returns the child notification at [index], or null if no such child.
+ */
@Nullable
public ExpandableNotificationRow getChildNotificationAt(int index) {
if (mChildrenContainer == null
@@ -834,7 +861,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/**
* @param isChildInGroup Is this notification now in a group
- * @param parent the new parent notification
+ * @param parent the new parent notification
*/
public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {
if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) {
@@ -898,7 +925,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren();
}
- /** Updates states of all children. */
+ /**
+ * Updates states of all children.
+ */
public void updateChildrenStates(AmbientState ambientState) {
if (mIsSummaryWithChildren) {
ExpandableViewState parentState = getViewState();
@@ -906,21 +935,27 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
- /** Applies children states. */
+ /**
+ * Applies children states.
+ */
public void applyChildrenState() {
if (mIsSummaryWithChildren) {
mChildrenContainer.applyState();
}
}
- /** Prepares expansion changed. */
+ /**
+ * Prepares expansion changed.
+ */
public void prepareExpansionChanged() {
if (mIsSummaryWithChildren) {
mChildrenContainer.prepareExpansionChanged();
}
}
- /** Starts child animations. */
+ /**
+ * Starts child animations.
+ */
public void startChildAnimation(AnimationProperties properties) {
if (mIsSummaryWithChildren) {
mChildrenContainer.startAnimationToState(properties);
@@ -984,7 +1019,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren) {
return mChildrenContainer.getIntrinsicHeight();
}
- if(mExpandedWhenPinned) {
+ if (mExpandedWhenPinned) {
return Math.max(getMaxExpandHeight(), getHeadsUpHeight());
} else if (atLeastMinHeight) {
return Math.max(getCollapsedHeight(), getHeadsUpHeight());
@@ -1079,18 +1114,22 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
updateClickAndFocus();
}
- /** The click listener for the bubble button. */
+ /**
+ * The click listener for the bubble button.
+ */
public View.OnClickListener getBubbleClickListener() {
return v -> {
if (mBubblesManagerOptional.isPresent()) {
mBubblesManagerOptional.get()
- .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);
+ .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);
}
mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
};
}
- /** The click listener for the snooze button. */
+ /**
+ * The click listener for the snooze button.
+ */
public View.OnClickListener getSnoozeClickListener(MenuItem item) {
return v -> {
// Dismiss a snoozed notification if one is still left behind
@@ -1252,7 +1291,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public void setContentBackground(int customBackgroundColor, boolean animate,
- NotificationContentView notificationContentView) {
+ NotificationContentView notificationContentView) {
if (getShowingLayout() == notificationContentView) {
setTintColor(customBackgroundColor, animate);
}
@@ -1487,7 +1526,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
l.setAlpha(alpha);
}
if (mChildrenContainer != null) {
- mChildrenContainer.setContentAlpha(alpha);
+ mChildrenContainer.setAlpha(alpha);
}
}
@@ -1637,7 +1676,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
setTargetPoint(null);
}
- /** Shows the given feedback icon, or hides the icon if null. */
+ /**
+ * Shows the given feedback icon, or hides the icon if null.
+ */
public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
if (mIsSummaryWithChildren) {
mChildrenContainer.setFeedbackIcon(icon);
@@ -1646,7 +1687,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mPublicLayout.setFeedbackIcon(icon);
}
- /** Sets the last time the notification being displayed audibly alerted the user. */
+ /**
+ * Sets the last time the notification being displayed audibly alerted the user.
+ */
public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs;
boolean alertedRecently = timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS;
@@ -1700,7 +1743,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
Trace.endSection();
}
- /** Generates and appends "(MessagingStyle)" type tag to passed string for tracing. */
+ /**
+ * Generates and appends "(MessagingStyle)" type tag to passed string for tracing.
+ */
@NonNull
private String appendTraceStyleTag(@NonNull String traceTag) {
if (!Trace.isEnabled()) {
@@ -1721,7 +1766,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
super.onFinishInflate();
mPublicLayout = findViewById(R.id.expandedPublic);
mPrivateLayout = findViewById(R.id.expanded);
- mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout};
+ mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
for (NotificationContentView l : mLayouts) {
l.setExpandClickListener(mExpandClickListener);
@@ -1740,6 +1785,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.setIsLowPriority(mIsLowPriority);
mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
mChildrenContainer.onNotificationUpdated();
+ mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
mTranslateableViews.add(mChildrenContainer);
});
@@ -1796,6 +1842,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/**
* Perform a smart action which triggers a longpress (expose guts).
* Based on the semanticAction passed, may update the state of the guts view.
+ *
* @param semanticAction associated with this smart action click
*/
public void doSmartActionClick(int x, int y, int semanticAction) {
@@ -1939,9 +1986,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/**
* Set the dismiss behavior of the view.
+ *
* @param usingRowTranslationX {@code true} if the view should translate using regular
- * translationX, otherwise the contents will be
- * translated.
+ * translationX, otherwise the contents will be
+ * translated.
*/
@Override
public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
@@ -1955,6 +2003,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (previousTranslation != 0) {
setTranslation(previousTranslation);
}
+ if (mChildrenContainer != null) {
+ List<ExpandableNotificationRow> notificationChildren =
+ mChildrenContainer.getAttachedChildren();
+ for (int i = 0; i < notificationChildren.size(); i++) {
+ ExpandableNotificationRow child = notificationChildren.get(i);
+ child.setDismissUsingRowTranslationX(usingRowTranslationX);
+ }
+ }
}
}
@@ -2009,7 +2065,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public Animator getTranslateViewAnimator(final float leftTarget,
- AnimatorUpdateListener listener) {
+ AnimatorUpdateListener listener) {
if (mTranslateAnim != null) {
mTranslateAnim.cancel();
}
@@ -2115,7 +2171,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
float startTop = params.getStartNotificationTop();
top = (int) Math.min(MathUtils.lerp(startTop,
- params.getTop(), expandProgress),
+ params.getTop(), expandProgress),
startTop);
} else {
top = params.getTop();
@@ -2151,29 +2207,30 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
setTranslationY(top);
- mTopRoundnessDuringLaunchAnimation = params.getTopCornerRadius() / mOutlineRadius;
- mBottomRoundnessDuringLaunchAnimation = params.getBottomCornerRadius() / mOutlineRadius;
+ final float maxRadius = getMaxRadius();
+ mTopRoundnessDuringLaunchAnimation = params.getTopCornerRadius() / maxRadius;
+ mBottomRoundnessDuringLaunchAnimation = params.getBottomCornerRadius() / maxRadius;
invalidateOutline();
mBackgroundNormal.setExpandAnimationSize(params.getWidth(), actualHeight);
}
@Override
- public float getCurrentTopRoundness() {
+ public float getTopRoundness() {
if (mExpandAnimationRunning) {
return mTopRoundnessDuringLaunchAnimation;
}
- return super.getCurrentTopRoundness();
+ return super.getTopRoundness();
}
@Override
- public float getCurrentBottomRoundness() {
+ public float getBottomRoundness() {
if (mExpandAnimationRunning) {
return mBottomRoundnessDuringLaunchAnimation;
}
- return super.getCurrentBottomRoundness();
+ return super.getBottomRoundness();
}
public void setExpandAnimationRunning(boolean expandAnimationRunning) {
@@ -2284,7 +2341,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/**
* Set this notification to be expanded by the user
*
- * @param userExpanded whether the user wants this notification to be expanded
+ * @param userExpanded whether the user wants this notification to be expanded
* @param allowChildExpansion whether a call to this method allows expanding children
*/
public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
@@ -2434,7 +2491,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/**
* @return {@code true} if the notification can show it's heads up layout. This is mostly true
- * except for legacy use cases.
+ * except for legacy use cases.
*/
public boolean canShowHeadsUp() {
if (mOnKeyguard && !isDozing() && !isBypassEnabled()) {
@@ -2625,7 +2682,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
- long duration) {
+ long duration) {
if (getVisibility() == GONE) {
// If we are GONE, the hideSensitive parameter will not be calculated and always be
// false, which is incorrect, let's wait until a real call comes in later.
@@ -2658,9 +2715,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private void animateShowingPublic(long delay, long duration, boolean showingPublic) {
View[] privateViews = mIsSummaryWithChildren
- ? new View[] {mChildrenContainer}
- : new View[] {mPrivateLayout};
- View[] publicViews = new View[] {mPublicLayout};
+ ? new View[]{mChildrenContainer}
+ : new View[]{mPrivateLayout};
+ View[] publicViews = new View[]{mPublicLayout};
View[] hiddenChildren = showingPublic ? privateViews : publicViews;
View[] shownChildren = showingPublic ? publicViews : privateViews;
for (final View hiddenView : hiddenChildren) {
@@ -2693,8 +2750,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/**
* @return Whether this view is allowed to be dismissed. Only valid for visible notifications as
- * otherwise some state might not be updated. To request about the general clearability
- * see {@link NotificationEntry#isDismissable()}.
+ * otherwise some state might not be updated. To request about the general clearability
+ * see {@link NotificationEntry#isDismissable()}.
*/
public boolean canViewBeDismissed() {
return mEntry.isDismissable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
@@ -2777,8 +2834,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
@Override
- public long performRemoveAnimation(long duration, long delay, float translationDirection,
- boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable,
+ public long performRemoveAnimation(
+ long duration,
+ long delay,
+ float translationDirection,
+ boolean isHeadsUpAnimation,
+ float endLocation,
+ Runnable onFinishedRunnable,
AnimatorListenerAdapter animationListener) {
if (mMenuRow != null && mMenuRow.isMenuVisible()) {
Animator anim = getTranslateViewAnimator(0f, null /* listener */);
@@ -2828,7 +2890,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
- /** Gets the last value set with {@link #setNotificationFaded(boolean)} */
+ /**
+ * Gets the last value set with {@link #setNotificationFaded(boolean)}
+ */
@Override
public boolean isNotificationFaded() {
return mIsFaded;
@@ -2843,7 +2907,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* notifications return false from {@link #hasOverlappingRendering()} and delegate the
* layerType to child views which really need it in order to render correctly, such as icon
* views or the conversation face pile.
- *
+ * <p>
* Another compounding factor for notifications is that we change clipping on each frame of the
* animation, so the hardware layer isn't able to do any caching at the top level, but the
* individual elements we render with hardware layers (e.g. icons) cache wonderfully because we
@@ -2869,7 +2933,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
- /** Private helper for iterating over the layouts and children containers to set faded state */
+ /**
+ * Private helper for iterating over the layouts and children containers to set faded state
+ */
private void setNotificationFadedOnChildren(boolean faded) {
delegateNotificationFaded(mChildrenContainer, faded);
for (NotificationContentView layout : mLayouts) {
@@ -2897,7 +2963,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* Because RemoteInputView is designed to be an opaque view that overlaps the Actions row, the
* row should require overlapping rendering to ensure that the overlapped view doesn't bleed
* through when alpha fading.
- *
+ * <p>
* Note that this currently works for top-level notifications which squish their height down
* while collapsing the shade, but does not work for children inside groups, because the
* accordion affect does not apply to those views, so super.hasOverlappingRendering() will
@@ -2976,7 +3042,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mGuts.getIntrinsicHeight();
} else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp
&& mHeadsUpManager.isTrackingHeadsUp()) {
- return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
+ return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
} else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) {
return mChildrenContainer.getMinHeight();
} else if (!ignoreTemporaryStates && canShowHeadsUp() && mIsHeadsUp) {
@@ -3218,8 +3284,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext());
if (snoozeMenu != null) {
AccessibilityAction action = new AccessibilityAction(R.id.action_snooze,
- getContext().getResources()
- .getString(R.string.notification_menu_snooze_action));
+ getContext().getResources()
+ .getString(R.string.notification_menu_snooze_action));
info.addAction(action);
}
}
@@ -3280,17 +3346,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
NotificationContentView contentView = (NotificationContentView) child;
if (isClippingNeeded()) {
return true;
- } else if (!hasNoRounding()
- && contentView.shouldClipToRounding(getCurrentTopRoundness() != 0.0f,
- getCurrentBottomRoundness() != 0.0f)) {
+ } else if (hasRoundedCorner()
+ && contentView.shouldClipToRounding(getTopRoundness() != 0.0f,
+ getBottomRoundness() != 0.0f)) {
return true;
}
} else if (child == mChildrenContainer) {
- if (isClippingNeeded() || !hasNoRounding()) {
+ if (isClippingNeeded() || hasRoundedCorner()) {
return true;
}
} else if (child instanceof NotificationGuts) {
- return !hasNoRounding();
+ return hasRoundedCorner();
}
return super.childNeedsClipping(child);
}
@@ -3316,14 +3382,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
@Override
- protected void applyRoundness() {
+ public void applyRoundness() {
super.applyRoundness();
applyChildrenRoundness();
}
private void applyChildrenRoundness() {
if (mIsSummaryWithChildren) {
- mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness());
+ mChildrenContainer.requestBottomRoundness(
+ getBottomRoundness(),
+ /* animate = */ false,
+ SourceType.DefaultValue);
}
}
@@ -3335,10 +3404,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return super.getCustomClipPath(child);
}
- private boolean hasNoRounding() {
- return getCurrentBottomRoundness() == 0.0f && getCurrentTopRoundness() == 0.0f;
- }
-
public boolean isMediaRow() {
return mEntry.getSbn().getNotification().isMediaNotification();
}
@@ -3434,6 +3499,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public interface LongPressListener {
/**
* Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
+ *
* @return whether the longpress was handled
*/
boolean onLongPress(View v, int x, int y, MenuItem item);
@@ -3455,6 +3521,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public interface CoordinateOnClickListener {
/**
* Equivalent to {@link View.OnClickListener#onClick(View)} with coordinates
+ *
* @return whether the click was handled
*/
boolean onClick(View v, int x, int y, MenuItem item);
@@ -3511,7 +3578,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private void setTargetPoint(Point p) {
mTargetPoint = p;
}
+
public Point getTargetPoint() {
return mTargetPoint;
}
+
+ /**
+ * Enable the support for rounded corner in notification group
+ * @param enabled true if is supported
+ */
+ public void enableNotificationGroupCorner(boolean enabled) {
+ mIsNotificationGroupCornerEnabled = enabled;
+ if (mChildrenContainer != null) {
+ mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
+ }
+ }
}
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 a493a676e3d8..842526ee0371 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
@@ -231,6 +231,8 @@ public class ExpandableNotificationRowController implements NotifViewController
mStatusBarStateController.removeCallback(mStatusBarStateListener);
}
});
+ mView.enableNotificationGroupCorner(
+ mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER));
}
private final StatusBarStateController.StateListener mStatusBarStateListener =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index d58fe3b3c4a3..4fde5d06f816 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -28,46 +28,21 @@ import android.view.View;
import android.view.ViewOutlineProvider;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.AnimatableProperty;
-import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.notification.RoundableState;
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
/**
* Like {@link ExpandableView}, but setting an outline for the height and clipping.
*/
public abstract class ExpandableOutlineView extends ExpandableView {
- private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from(
- "topRoundness",
- ExpandableOutlineView::setTopRoundnessInternal,
- ExpandableOutlineView::getCurrentTopRoundness,
- R.id.top_roundess_animator_tag,
- R.id.top_roundess_animator_end_tag,
- R.id.top_roundess_animator_start_tag);
- private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from(
- "bottomRoundness",
- ExpandableOutlineView::setBottomRoundnessInternal,
- ExpandableOutlineView::getCurrentBottomRoundness,
- R.id.bottom_roundess_animator_tag,
- R.id.bottom_roundess_animator_end_tag,
- R.id.bottom_roundess_animator_start_tag);
- private static final AnimationProperties ROUNDNESS_PROPERTIES =
- new AnimationProperties().setDuration(
- StackStateAnimator.ANIMATION_DURATION_CORNER_RADIUS);
+ private RoundableState mRoundableState;
private static final Path EMPTY_PATH = new Path();
-
private final Rect mOutlineRect = new Rect();
- private final Path mClipPath = new Path();
private boolean mCustomOutline;
private float mOutlineAlpha = -1f;
- protected float mOutlineRadius;
private boolean mAlwaysRoundBothCorners;
private Path mTmpPath = new Path();
- private float mCurrentBottomRoundness;
- private float mCurrentTopRoundness;
- private float mBottomRoundness;
- private float mTopRoundness;
private int mBackgroundTop;
/**
@@ -80,8 +55,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- if (!mCustomOutline && getCurrentTopRoundness() == 0.0f
- && getCurrentBottomRoundness() == 0.0f && !mAlwaysRoundBothCorners) {
+ if (!mCustomOutline && !hasRoundedCorner() && !mAlwaysRoundBothCorners) {
// Only when translating just the contents, does the outline need to be shifted.
int translation = !mDismissUsingRowTranslationX ? (int) getTranslation() : 0;
int left = Math.max(translation, 0);
@@ -99,14 +73,18 @@ public abstract class ExpandableOutlineView extends ExpandableView {
}
};
+ @Override
+ public RoundableState getRoundableState() {
+ return mRoundableState;
+ }
+
protected Path getClipPath(boolean ignoreTranslation) {
int left;
int top;
int right;
int bottom;
int height;
- float topRoundness = mAlwaysRoundBothCorners
- ? mOutlineRadius : getCurrentBackgroundRadiusTop();
+ float topRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getTopCornerRadius();
if (!mCustomOutline) {
// The outline just needs to be shifted if we're translating the contents. Otherwise
// it's already in the right place.
@@ -130,12 +108,11 @@ public abstract class ExpandableOutlineView extends ExpandableView {
if (height == 0) {
return EMPTY_PATH;
}
- float bottomRoundness = mAlwaysRoundBothCorners
- ? mOutlineRadius : getCurrentBackgroundRadiusBottom();
+ float bottomRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
if (topRoundness + bottomRoundness > height) {
float overShoot = topRoundness + bottomRoundness - height;
- float currentTopRoundness = getCurrentTopRoundness();
- float currentBottomRoundness = getCurrentBottomRoundness();
+ float currentTopRoundness = getTopRoundness();
+ float currentBottomRoundness = getBottomRoundness();
topRoundness -= overShoot * currentTopRoundness
/ (currentTopRoundness + currentBottomRoundness);
bottomRoundness -= overShoot * currentBottomRoundness
@@ -145,8 +122,18 @@ public abstract class ExpandableOutlineView extends ExpandableView {
return mTmpPath;
}
- public void getRoundedRectPath(int left, int top, int right, int bottom,
- float topRoundness, float bottomRoundness, Path outPath) {
+ /**
+ * Add a round rect in {@code outPath}
+ * @param outPath destination path
+ */
+ public void getRoundedRectPath(
+ int left,
+ int top,
+ int right,
+ int bottom,
+ float topRoundness,
+ float bottomRoundness,
+ Path outPath) {
outPath.reset();
mTmpCornerRadii[0] = topRoundness;
mTmpCornerRadii[1] = topRoundness;
@@ -168,15 +155,28 @@ public abstract class ExpandableOutlineView extends ExpandableView {
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
canvas.save();
+ Path clipPath = null;
+ Path childClipPath = null;
if (childNeedsClipping(child)) {
- Path clipPath = getCustomClipPath(child);
+ clipPath = getCustomClipPath(child);
if (clipPath == null) {
clipPath = getClipPath(false /* ignoreTranslation */);
}
- if (clipPath != null) {
- canvas.clipPath(clipPath);
+ // If the notification uses "RowTranslationX" as dismiss behavior, we should clip the
+ // children instead.
+ if (mDismissUsingRowTranslationX && child instanceof NotificationChildrenContainer) {
+ childClipPath = clipPath;
+ clipPath = null;
}
}
+
+ if (child instanceof NotificationChildrenContainer) {
+ ((NotificationChildrenContainer) child).setChildClipPath(childClipPath);
+ }
+ if (clipPath != null) {
+ canvas.clipPath(clipPath);
+ }
+
boolean result = super.drawChild(canvas, child, drawingTime);
canvas.restore();
return result;
@@ -207,73 +207,21 @@ public abstract class ExpandableOutlineView extends ExpandableView {
private void initDimens() {
Resources res = getResources();
- mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
- if (!mAlwaysRoundBothCorners) {
- mOutlineRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius);
+ float maxRadius;
+ if (mAlwaysRoundBothCorners) {
+ maxRadius = res.getDimension(R.dimen.notification_shadow_radius);
+ } else {
+ maxRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius);
}
+ mRoundableState = new RoundableState(this, this, maxRadius);
setClipToOutline(mAlwaysRoundBothCorners);
}
@Override
- public boolean setTopRoundness(float topRoundness, boolean animate) {
- if (mTopRoundness != topRoundness) {
- float diff = Math.abs(topRoundness - mTopRoundness);
- mTopRoundness = topRoundness;
- boolean shouldAnimate = animate;
- if (PropertyAnimator.isAnimating(this, TOP_ROUNDNESS) && diff > 0.5f) {
- // Fail safe:
- // when we've been animating previously and we're now getting an update in the
- // other direction, make sure to animate it too, otherwise, the localized updating
- // may make the start larger than 1.0.
- shouldAnimate = true;
- }
- PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness,
- ROUNDNESS_PROPERTIES, shouldAnimate);
- return true;
- }
- return false;
- }
-
- protected void applyRoundness() {
+ public void applyRoundness() {
invalidateOutline();
- invalidate();
- }
-
- public float getCurrentBackgroundRadiusTop() {
- return getCurrentTopRoundness() * mOutlineRadius;
- }
-
- public float getCurrentTopRoundness() {
- return mCurrentTopRoundness;
- }
-
- public float getCurrentBottomRoundness() {
- return mCurrentBottomRoundness;
- }
-
- public float getCurrentBackgroundRadiusBottom() {
- return getCurrentBottomRoundness() * mOutlineRadius;
- }
-
- @Override
- public boolean setBottomRoundness(float bottomRoundness, boolean animate) {
- if (mBottomRoundness != bottomRoundness) {
- float diff = Math.abs(bottomRoundness - mBottomRoundness);
- mBottomRoundness = bottomRoundness;
- boolean shouldAnimate = animate;
- if (PropertyAnimator.isAnimating(this, BOTTOM_ROUNDNESS) && diff > 0.5f) {
- // Fail safe:
- // when we've been animating previously and we're now getting an update in the
- // other direction, make sure to animate it too, otherwise, the localized updating
- // may make the start larger than 1.0.
- shouldAnimate = true;
- }
- PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness,
- ROUNDNESS_PROPERTIES, shouldAnimate);
- return true;
- }
- return false;
+ super.applyRoundness();
}
protected void setBackgroundTop(int backgroundTop) {
@@ -283,16 +231,6 @@ public abstract class ExpandableOutlineView extends ExpandableView {
}
}
- private void setTopRoundnessInternal(float topRoundness) {
- mCurrentTopRoundness = topRoundness;
- applyRoundness();
- }
-
- private void setBottomRoundnessInternal(float bottomRoundness) {
- mCurrentBottomRoundness = bottomRoundness;
- applyRoundness();
- }
-
public void onDensityOrFontScaleChanged() {
initDimens();
applyRoundness();
@@ -348,9 +286,10 @@ public abstract class ExpandableOutlineView extends ExpandableView {
/**
* Set the dismiss behavior of the view.
+ *
* @param usingRowTranslationX {@code true} if the view should translate using regular
- * translationX, otherwise the contents will be
- * translated.
+ * translationX, otherwise the contents will be
+ * translated.
*/
public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
mDismissUsingRowTranslationX = usingRowTranslationX;
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 38f0c550d4fc..955d7c18f870 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
@@ -36,6 +36,8 @@ import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.notification.Roundable;
+import com.android.systemui.statusbar.notification.RoundableState;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.util.DumpUtilsKt;
@@ -47,9 +49,10 @@ import java.util.List;
/**
* An abstract view for expandable views.
*/
-public abstract class ExpandableView extends FrameLayout implements Dumpable {
+public abstract class ExpandableView extends FrameLayout implements Dumpable, Roundable {
private static final String TAG = "ExpandableView";
+ private RoundableState mRoundableState = null;
protected OnHeightChangedListener mOnHeightChangedListener;
private int mActualHeight;
protected int mClipTopAmount;
@@ -78,6 +81,14 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
initDimens();
}
+ @Override
+ public RoundableState getRoundableState() {
+ if (mRoundableState == null) {
+ mRoundableState = new RoundableState(this, this, 0f);
+ }
+ return mRoundableState;
+ }
+
private void initDimens() {
mContentShift = getResources().getDimensionPixelSize(
R.dimen.shelf_transform_content_shift);
@@ -440,8 +451,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
int top = getClipTopAmount();
int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
- mClipBottomAmount, top), mMinimumHeightForClipping);
- int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f);
- mClipRect.set(-halfExtraWidth, top, getWidth() + halfExtraWidth, bottom);
+ mClipRect.set(Integer.MIN_VALUE, top, Integer.MAX_VALUE, bottom);
setClipBounds(mClipRect);
} else {
setClipBounds(null);
@@ -455,7 +465,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
public void setExtraWidthForClipping(float extraWidthForClipping) {
mExtraWidthForClipping = extraWidthForClipping;
- updateClipping();
}
public float getHeaderVisibleAmount() {
@@ -844,22 +853,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
return mFirstInSection;
}
- /**
- * Set the topRoundness of this view.
- * @return Whether the roundness was changed.
- */
- public boolean setTopRoundness(float topRoundness, boolean animate) {
- return false;
- }
-
- /**
- * Set the bottom roundness of this view.
- * @return Whether the roundness was changed.
- */
- public boolean setBottomRoundness(float bottomRoundness, boolean animate) {
- return false;
- }
-
public int getHeadsUpHeightWithoutHeader() {
return getHeight();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index ab91926d466a..46fef3f973a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.row
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 4c693045bc88..c534860d12c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -40,7 +40,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
index f9923b2254d7..8a5d29a1ae2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.row
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 7a654365e0ae..f13e48d55ae4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -35,12 +35,15 @@ import androidx.annotation.Nullable;
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.NotificationExpandButton;
+import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.ImageTransformState;
+import com.android.systemui.statusbar.notification.Roundable;
+import com.android.systemui.statusbar.notification.RoundableState;
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -49,13 +52,12 @@ import java.util.Stack;
/**
* Wraps a notification view which may or may not include a header.
*/
-public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
+public class NotificationHeaderViewWrapper extends NotificationViewWrapper implements Roundable {
+ private final RoundableState mRoundableState;
private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
= new PathInterpolator(0.4f, 0f, 0.7f, 1f);
-
protected final ViewTransformationHelper mTransformationHelper;
-
private CachingIconView mIcon;
private NotificationExpandButton mExpandButton;
private View mAltExpandTarget;
@@ -67,12 +69,16 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
private ImageView mWorkProfileImage;
private View mAudiblyAlertedIcon;
private View mFeedbackIcon;
-
private boolean mIsLowPriority;
private boolean mTransformLowPriorityTitle;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row);
+ mRoundableState = new RoundableState(
+ mView,
+ this,
+ ctx.getResources().getDimension(R.dimen.notification_corner_radius)
+ );
mTransformationHelper = new ViewTransformationHelper();
// we want to avoid that the header clashes with the other text when transforming
@@ -81,7 +87,8 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
new CustomInterpolatorTransformation(TRANSFORMING_VIEW_TITLE) {
@Override
- public Interpolator getCustomInterpolator(int interpolationType,
+ public Interpolator getCustomInterpolator(
+ int interpolationType,
boolean isFrom) {
boolean isLowPriority = mView instanceof NotificationHeaderView;
if (interpolationType == TRANSFORM_Y) {
@@ -99,11 +106,17 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
protected boolean hasCustomTransformation() {
return mIsLowPriority && mTransformLowPriorityTitle;
}
- }, TRANSFORMING_VIEW_TITLE);
+ },
+ TRANSFORMING_VIEW_TITLE);
resolveHeaderViews();
addFeedbackOnClickListener(row);
}
+ @Override
+ public RoundableState getRoundableState() {
+ return mRoundableState;
+ }
+
protected void resolveHeaderViews() {
mIcon = mView.findViewById(com.android.internal.R.id.icon);
mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
@@ -128,7 +141,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
}
}
- /** Shows the given feedback icon, or hides the icon if null. */
+ /**
+ * Shows the given feedback icon, or hides the icon if null.
+ */
@Override
public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
if (mFeedbackIcon != null) {
@@ -193,7 +208,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
// its animation
&& child.getId() != com.android.internal.R.id.conversation_icon_badge_ring) {
((ImageView) child).setCropToPadding(true);
- } else if (child instanceof ViewGroup){
+ } else if (child instanceof ViewGroup) {
ViewGroup group = (ViewGroup) child;
for (int i = 0; i < group.getChildCount(); i++) {
stack.push(group.getChildAt(i));
@@ -215,7 +230,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
}
@Override
- public void updateExpandability(boolean expandable, View.OnClickListener onClickListener,
+ public void updateExpandability(
+ boolean expandable,
+ View.OnClickListener onClickListener,
boolean requestLayout) {
mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
mExpandButton.setOnClickListener(expandable ? onClickListener : null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 2719dd88b7be..b2628e40e77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -142,6 +142,11 @@ public class AmbientState implements Dumpable {
*/
private boolean mIsFlingRequiredAfterLockScreenSwipeUp = false;
+ /**
+ * Whether the shade is currently closing.
+ */
+ private boolean mIsClosing;
+
@VisibleForTesting
public boolean isFlingRequiredAfterLockScreenSwipeUp() {
return mIsFlingRequiredAfterLockScreenSwipeUp;
@@ -717,6 +722,20 @@ public class AmbientState implements Dumpable {
&& mStatusBarKeyguardViewManager.isBouncerInTransit();
}
+ /**
+ * @param isClosing Whether the shade is currently closing.
+ */
+ public void setIsClosing(boolean isClosing) {
+ mIsClosing = isClosing;
+ }
+
+ /**
+ * @return Whether the shade is currently closing.
+ */
+ public boolean isClosing() {
+ return mIsClosing;
+ }
+
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("mTopPadding=" + mTopPadding);
@@ -761,5 +780,6 @@ public class AmbientState implements Dumpable {
+ mIsFlingRequiredAfterLockScreenSwipeUp);
pw.println("mZDistanceBetweenElements=" + mZDistanceBetweenElements);
pw.println("mBaseZHeight=" + mBaseZHeight);
+ pw.println("mIsClosing=" + mIsClosing);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 0dda2632db66..26f0ad9eca87 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
@@ -21,6 +21,9 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.Path.Direction;
import android.graphics.drawable.ColorDrawable;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
@@ -33,6 +36,7 @@ import android.view.ViewGroup;
import android.widget.RemoteViews;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -43,10 +47,14 @@ import com.android.systemui.statusbar.NotificationGroupingUtil;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.Roundable;
+import com.android.systemui.statusbar.notification.RoundableState;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.HybridGroupManager;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import java.util.ArrayList;
@@ -56,7 +64,7 @@ import java.util.List;
* A container containing child notifications
*/
public class NotificationChildrenContainer extends ViewGroup
- implements NotificationFadeAware {
+ implements NotificationFadeAware, Roundable {
private static final String TAG = "NotificationChildrenContainer";
@@ -100,9 +108,9 @@ public class NotificationChildrenContainer extends ViewGroup
private boolean mEnableShadowOnChildNotifications;
private NotificationHeaderView mNotificationHeader;
- private NotificationViewWrapper mNotificationHeaderWrapper;
+ private NotificationHeaderViewWrapper mNotificationHeaderWrapper;
private NotificationHeaderView mNotificationHeaderLowPriority;
- private NotificationViewWrapper mNotificationHeaderWrapperLowPriority;
+ private NotificationHeaderViewWrapper mNotificationHeaderWrapperLowPriority;
private NotificationGroupingUtil mGroupingUtil;
private ViewState mHeaderViewState;
private int mClipBottomAmount;
@@ -110,7 +118,8 @@ public class NotificationChildrenContainer extends ViewGroup
private OnClickListener mHeaderClickListener;
private ViewGroup mCurrentHeader;
private boolean mIsConversation;
-
+ private Path mChildClipPath = null;
+ private final Path mHeaderPath = new Path();
private boolean mShowGroupCountInExpander;
private boolean mShowDividersWhenExpanded;
private boolean mHideDividersDuringExpand;
@@ -119,6 +128,8 @@ public class NotificationChildrenContainer extends ViewGroup
private float mHeaderVisibleAmount = 1.0f;
private int mUntruncatedChildCount;
private boolean mContainingNotificationIsFaded = false;
+ private RoundableState mRoundableState;
+ private boolean mIsNotificationGroupCornerEnabled;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -132,10 +143,14 @@ public class NotificationChildrenContainer extends ViewGroup
this(context, attrs, defStyleAttr, 0);
}
- public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
+ public NotificationChildrenContainer(
+ Context context,
+ AttributeSet attrs,
+ int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mHybridGroupManager = new HybridGroupManager(getContext());
+ mRoundableState = new RoundableState(this, this, 0f);
initDimens();
setClipChildren(false);
}
@@ -167,6 +182,12 @@ public class NotificationChildrenContainer extends ViewGroup
mHybridGroupManager.initDimens();
}
+ @NonNull
+ @Override
+ public RoundableState getRoundableState() {
+ return mRoundableState;
+ }
+
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount =
@@ -271,7 +292,7 @@ public class NotificationChildrenContainer extends ViewGroup
/**
* Add a child notification to this view.
*
- * @param row the row to add
+ * @param row the row to add
* @param childIndex the index to add it at, if -1 it will be added at the end
*/
public void addNotification(ExpandableNotificationRow row, int childIndex) {
@@ -347,8 +368,11 @@ public class NotificationChildrenContainer extends ViewGroup
mNotificationHeader.findViewById(com.android.internal.R.id.expand_button)
.setVisibility(VISIBLE);
mNotificationHeader.setOnClickListener(mHeaderClickListener);
- mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(),
- mNotificationHeader, mContainingNotification);
+ mNotificationHeaderWrapper =
+ (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap(
+ getContext(),
+ mNotificationHeader,
+ mContainingNotification);
addView(mNotificationHeader, 0);
invalidate();
} else {
@@ -381,8 +405,11 @@ public class NotificationChildrenContainer extends ViewGroup
mNotificationHeaderLowPriority.findViewById(com.android.internal.R.id.expand_button)
.setVisibility(VISIBLE);
mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener);
- mNotificationHeaderWrapperLowPriority = NotificationViewWrapper.wrap(getContext(),
- mNotificationHeaderLowPriority, mContainingNotification);
+ mNotificationHeaderWrapperLowPriority =
+ (NotificationHeaderViewWrapper) NotificationViewWrapper.wrap(
+ getContext(),
+ mNotificationHeaderLowPriority,
+ mContainingNotification);
addView(mNotificationHeaderLowPriority, 0);
invalidate();
} else {
@@ -462,20 +489,8 @@ public class NotificationChildrenContainer extends ViewGroup
}
/**
- * Sets the alpha on the content, while leaving the background of the container itself as is.
- *
- * @param alpha alpha value to apply to the content
+ * To be called any time the rows have been updated
*/
- public void setContentAlpha(float alpha) {
- for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
- mNotificationHeader.getChildAt(i).setAlpha(alpha);
- }
- for (ExpandableNotificationRow child : getAttachedChildren()) {
- child.setContentAlpha(alpha);
- }
- }
-
- /** To be called any time the rows have been updated */
public void updateExpansionStates() {
if (mChildrenExpanded || mUserLocked) {
// we don't modify it the group is expanded or if we are expanding it
@@ -489,7 +504,6 @@ public class NotificationChildrenContainer extends ViewGroup
}
/**
- *
* @return the intrinsic size of this children container, i.e the natural fully expanded state
*/
public int getIntrinsicHeight() {
@@ -499,7 +513,7 @@ public class NotificationChildrenContainer extends ViewGroup
/**
* @return the intrinsic height with a number of children given
- * in @param maxAllowedVisibleChildren
+ * in @param maxAllowedVisibleChildren
*/
private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
if (showingAsLowPriority()) {
@@ -553,7 +567,8 @@ public class NotificationChildrenContainer extends ViewGroup
/**
* Update the state of all its children based on a linear layout algorithm.
- * @param parentState the state of the parent
+ *
+ * @param parentState the state of the parent
* @param ambientState the ambient state containing ambient information
*/
public void updateState(ExpandableViewState parentState, AmbientState ambientState) {
@@ -669,14 +684,17 @@ public class NotificationChildrenContainer extends ViewGroup
* When moving into the bottom stack, the bottom visible child in an expanded group adjusts its
* height, children in the group after this are gone.
*
- * @param child the child who's height to adjust.
+ * @param child the child who's height to adjust.
* @param parentHeight the height of the parent.
- * @param childState the state to update.
- * @param yPosition the yPosition of the view.
+ * @param childState the state to update.
+ * @param yPosition the yPosition of the view.
* @return true if children after this one should be hidden.
*/
- private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child,
- int parentHeight, ExpandableViewState childState, int yPosition) {
+ private boolean updateChildStateForExpandedGroup(
+ ExpandableNotificationRow child,
+ int parentHeight,
+ ExpandableViewState childState,
+ int yPosition) {
final int top = yPosition + child.getClipTopAmount();
final int intrinsicHeight = child.getIntrinsicHeight();
final int bottom = top + intrinsicHeight;
@@ -704,13 +722,15 @@ public class NotificationChildrenContainer extends ViewGroup
if (mIsLowPriority
|| (!mContainingNotification.isOnKeyguard() && mContainingNotification.isExpanded())
|| (mContainingNotification.isHeadsUpState()
- && mContainingNotification.canShowHeadsUp())) {
+ && mContainingNotification.canShowHeadsUp())) {
return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
}
return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
}
- /** Applies state to children. */
+ /**
+ * Applies state to children.
+ */
public void applyState() {
int childCount = mAttachedChildren.size();
ViewState tmpState = new ViewState();
@@ -782,17 +802,73 @@ public class NotificationChildrenContainer extends ViewGroup
}
}
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ boolean isCanvasChanged = false;
+
+ Path clipPath = mChildClipPath;
+ if (clipPath != null) {
+ final float translation;
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) child;
+ translation = notificationRow.getTranslation();
+ } else {
+ translation = child.getTranslationX();
+ }
+
+ isCanvasChanged = true;
+ canvas.save();
+ if (mIsNotificationGroupCornerEnabled && translation != 0f) {
+ clipPath.offset(translation, 0f);
+ canvas.clipPath(clipPath);
+ clipPath.offset(-translation, 0f);
+ } else {
+ canvas.clipPath(clipPath);
+ }
+ }
+
+ if (child instanceof NotificationHeaderView
+ && mNotificationHeaderWrapper.hasRoundedCorner()) {
+ float[] radii = mNotificationHeaderWrapper.getUpdatedRadii();
+ mHeaderPath.reset();
+ mHeaderPath.addRoundRect(
+ child.getLeft(),
+ child.getTop(),
+ child.getRight(),
+ child.getBottom(),
+ radii,
+ Direction.CW
+ );
+ if (!isCanvasChanged) {
+ isCanvasChanged = true;
+ canvas.save();
+ }
+ canvas.clipPath(mHeaderPath);
+ }
+
+ if (isCanvasChanged) {
+ boolean result = super.drawChild(canvas, child, drawingTime);
+ canvas.restore();
+ return result;
+ } else {
+ // If there have been no changes to the canvas we can proceed as usual
+ return super.drawChild(canvas, child, drawingTime);
+ }
+ }
+
+
/**
* This is called when the children expansion has changed and positions the children properly
* for an appear animation.
- *
*/
public void prepareExpansionChanged() {
// TODO: do something that makes sense, like placing the invisible views correctly
return;
}
- /** Animate to a given state. */
+ /**
+ * Animate to a given state.
+ */
public void startAnimationToState(AnimationProperties properties) {
int childCount = mAttachedChildren.size();
ViewState tmpState = new ViewState();
@@ -1116,7 +1192,8 @@ public class NotificationChildrenContainer extends ViewGroup
* Get the minimum Height for this group.
*
* @param maxAllowedVisibleChildren the number of children that should be visible
- * @param likeHighPriority if the height should be calculated as if it were not low priority
+ * @param likeHighPriority if the height should be calculated as if it were not low
+ * priority
*/
private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority) {
return getMinHeight(maxAllowedVisibleChildren, likeHighPriority, mCurrentHeaderTranslation);
@@ -1126,10 +1203,13 @@ public class NotificationChildrenContainer extends ViewGroup
* Get the minimum Height for this group.
*
* @param maxAllowedVisibleChildren the number of children that should be visible
- * @param likeHighPriority if the height should be calculated as if it were not low priority
- * @param headerTranslation the translation amount of the header
+ * @param likeHighPriority if the height should be calculated as if it were not low
+ * priority
+ * @param headerTranslation the translation amount of the header
*/
- private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority,
+ private int getMinHeight(
+ int maxAllowedVisibleChildren,
+ boolean likeHighPriority,
int headerTranslation) {
if (!likeHighPriority && showingAsLowPriority()) {
if (mNotificationHeaderLowPriority == null) {
@@ -1288,16 +1368,19 @@ public class NotificationChildrenContainer extends ViewGroup
return mUserLocked;
}
- public void setCurrentBottomRoundness(float currentBottomRoundness) {
+ @Override
+ public void applyRoundness() {
+ Roundable.super.applyRoundness();
boolean last = true;
for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
ExpandableNotificationRow child = mAttachedChildren.get(i);
if (child.getVisibility() == View.GONE) {
continue;
}
- float bottomRoundness = last ? currentBottomRoundness : 0.0f;
- child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
- child.setTopRoundness(0.0f, false /* animate */);
+ child.requestBottomRoundness(
+ last ? getBottomRoundness() : 0f,
+ /* animate = */ isShown(),
+ SourceType.DefaultValue);
last = false;
}
}
@@ -1307,7 +1390,9 @@ public class NotificationChildrenContainer extends ViewGroup
mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader);
}
- /** Shows the given feedback icon, or hides the icon if null. */
+ /**
+ * Shows the given feedback icon, or hides the icon if null.
+ */
public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
if (mNotificationHeaderWrapper != null) {
mNotificationHeaderWrapper.setFeedbackIcon(icon);
@@ -1339,4 +1424,26 @@ public class NotificationChildrenContainer extends ViewGroup
child.setNotificationFaded(faded);
}
}
+
+ /**
+ * Allow to define a path the clip the children in #drawChild()
+ *
+ * @param childClipPath path used to clip the children
+ */
+ public void setChildClipPath(@Nullable Path childClipPath) {
+ mChildClipPath = childClipPath;
+ invalidate();
+ }
+
+ public NotificationHeaderViewWrapper getNotificationHeaderWrapper() {
+ return mNotificationHeaderWrapper;
+ }
+
+ /**
+ * Enable the support for rounded corner in notification group
+ * @param enabled true if is supported
+ */
+ public void enableNotificationGroupCorner(boolean enabled) {
+ mIsNotificationGroupCornerEnabled = enabled;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 2015c87aac2b..6810055ad3bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -26,6 +26,8 @@ import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
+import com.android.systemui.statusbar.notification.Roundable;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -59,8 +61,8 @@ public class NotificationRoundnessManager implements Dumpable {
private boolean mIsClearAllInProgress;
private ExpandableView mSwipedView = null;
- private ExpandableView mViewBeforeSwipedView = null;
- private ExpandableView mViewAfterSwipedView = null;
+ private Roundable mViewBeforeSwipedView = null;
+ private Roundable mViewAfterSwipedView = null;
@Inject
NotificationRoundnessManager(
@@ -101,11 +103,12 @@ public class NotificationRoundnessManager implements Dumpable {
public boolean isViewAffectedBySwipe(ExpandableView expandableView) {
return expandableView != null
&& (expandableView == mSwipedView
- || expandableView == mViewBeforeSwipedView
- || expandableView == mViewAfterSwipedView);
+ || expandableView == mViewBeforeSwipedView
+ || expandableView == mViewAfterSwipedView);
}
- boolean updateViewWithoutCallback(ExpandableView view,
+ boolean updateViewWithoutCallback(
+ ExpandableView view,
boolean animate) {
if (view == null
|| view == mViewBeforeSwipedView
@@ -113,11 +116,15 @@ public class NotificationRoundnessManager implements Dumpable {
return false;
}
- final float topRoundness = getRoundnessFraction(view, true /* top */);
- final float bottomRoundness = getRoundnessFraction(view, false /* top */);
+ final boolean isTopChanged = view.requestTopRoundness(
+ getRoundnessDefaultValue(view, true /* top */),
+ animate,
+ SourceType.DefaultValue);
- final boolean topChanged = view.setTopRoundness(topRoundness, animate);
- final boolean bottomChanged = view.setBottomRoundness(bottomRoundness, animate);
+ final boolean isBottomChanged = view.requestBottomRoundness(
+ getRoundnessDefaultValue(view, /* top = */ false),
+ animate,
+ SourceType.DefaultValue);
final boolean isFirstInSection = isFirstInSection(view);
final boolean isLastInSection = isLastInSection(view);
@@ -126,9 +133,9 @@ public class NotificationRoundnessManager implements Dumpable {
view.setLastInSection(isLastInSection);
mNotifLogger.onCornersUpdated(view, isFirstInSection,
- isLastInSection, topChanged, bottomChanged);
+ isLastInSection, isTopChanged, isBottomChanged);
- return (isFirstInSection || isLastInSection) && (topChanged || bottomChanged);
+ return (isFirstInSection || isLastInSection) && (isTopChanged || isBottomChanged);
}
private boolean isFirstInSection(ExpandableView view) {
@@ -150,42 +157,46 @@ public class NotificationRoundnessManager implements Dumpable {
}
void setViewsAffectedBySwipe(
- ExpandableView viewBefore,
+ Roundable viewBefore,
ExpandableView viewSwiped,
- ExpandableView viewAfter) {
+ Roundable viewAfter) {
final boolean animate = true;
+ final SourceType source = SourceType.OnDismissAnimation;
+
+ // This method requires you to change the roundness of the current View targets and reset
+ // the roundness of the old View targets (if any) to 0f.
+ // To avoid conflicts, it generates a set of old Views and removes the current Views
+ // from this set.
+ HashSet<Roundable> oldViews = new HashSet<>();
+ if (mViewBeforeSwipedView != null) oldViews.add(mViewBeforeSwipedView);
+ if (mSwipedView != null) oldViews.add(mSwipedView);
+ if (mViewAfterSwipedView != null) oldViews.add(mViewAfterSwipedView);
- ExpandableView oldViewBefore = mViewBeforeSwipedView;
mViewBeforeSwipedView = viewBefore;
- if (oldViewBefore != null) {
- final float bottomRoundness = getRoundnessFraction(oldViewBefore, false /* top */);
- oldViewBefore.setBottomRoundness(bottomRoundness, animate);
- }
if (viewBefore != null) {
- viewBefore.setBottomRoundness(1f, animate);
+ oldViews.remove(viewBefore);
+ viewBefore.requestTopRoundness(0f, animate, source);
+ viewBefore.requestBottomRoundness(1f, animate, source);
}
- ExpandableView oldSwipedview = mSwipedView;
mSwipedView = viewSwiped;
- if (oldSwipedview != null) {
- final float bottomRoundness = getRoundnessFraction(oldSwipedview, false /* top */);
- final float topRoundness = getRoundnessFraction(oldSwipedview, true /* top */);
- oldSwipedview.setTopRoundness(topRoundness, animate);
- oldSwipedview.setBottomRoundness(bottomRoundness, animate);
- }
if (viewSwiped != null) {
- viewSwiped.setTopRoundness(1f, animate);
- viewSwiped.setBottomRoundness(1f, animate);
+ oldViews.remove(viewSwiped);
+ viewSwiped.requestTopRoundness(1f, animate, source);
+ viewSwiped.requestBottomRoundness(1f, animate, source);
}
- ExpandableView oldViewAfter = mViewAfterSwipedView;
mViewAfterSwipedView = viewAfter;
- if (oldViewAfter != null) {
- final float topRoundness = getRoundnessFraction(oldViewAfter, true /* top */);
- oldViewAfter.setTopRoundness(topRoundness, animate);
- }
if (viewAfter != null) {
- viewAfter.setTopRoundness(1f, animate);
+ oldViews.remove(viewAfter);
+ viewAfter.requestTopRoundness(1f, animate, source);
+ viewAfter.requestBottomRoundness(0f, animate, source);
+ }
+
+ // After setting the current Views, reset the views that are still present in the set.
+ for (Roundable oldView : oldViews) {
+ oldView.requestTopRoundness(0f, animate, source);
+ oldView.requestBottomRoundness(0f, animate, source);
}
}
@@ -193,7 +204,7 @@ public class NotificationRoundnessManager implements Dumpable {
mIsClearAllInProgress = isClearingAll;
}
- private float getRoundnessFraction(ExpandableView view, boolean top) {
+ private float getRoundnessDefaultValue(Roundable view, boolean top) {
if (view == null) {
return 0f;
}
@@ -207,28 +218,35 @@ public class NotificationRoundnessManager implements Dumpable {
&& mIsClearAllInProgress) {
return 1.0f;
}
- if ((view.isPinned()
- || (view.isHeadsUpAnimatingAway()) && !mExpanded)) {
- return 1.0f;
- }
- if (isFirstInSection(view) && top) {
- return 1.0f;
- }
- if (isLastInSection(view) && !top) {
- return 1.0f;
- }
+ if (view instanceof ExpandableView) {
+ ExpandableView expandableView = (ExpandableView) view;
+ if ((expandableView.isPinned()
+ || (expandableView.isHeadsUpAnimatingAway()) && !mExpanded)) {
+ return 1.0f;
+ }
+ if (isFirstInSection(expandableView) && top) {
+ return 1.0f;
+ }
+ if (isLastInSection(expandableView) && !top) {
+ return 1.0f;
+ }
- if (view == mTrackedHeadsUp) {
- // If we're pushing up on a headsup the appear fraction is < 0 and it needs to still be
- // rounded.
- return MathUtils.saturate(1.0f - mAppearFraction);
- }
- if (view.showingPulsing() && mRoundForPulsingViews) {
- return 1.0f;
+ if (view == mTrackedHeadsUp) {
+ // If we're pushing up on a headsup the appear fraction is < 0 and it needs to
+ // still be rounded.
+ return MathUtils.saturate(1.0f - mAppearFraction);
+ }
+ if (expandableView.showingPulsing() && mRoundForPulsingViews) {
+ return 1.0f;
+ }
+ if (expandableView.isChildInGroup()) {
+ return 0f;
+ }
+ final Resources resources = expandableView.getResources();
+ return resources.getDimension(R.dimen.notification_corner_radius_small)
+ / resources.getDimension(R.dimen.notification_corner_radius);
}
- final Resources resources = view.getResources();
- return resources.getDimension(R.dimen.notification_corner_radius_small)
- / resources.getDimension(R.dimen.notification_corner_radius);
+ return 0f;
}
public void setExpanded(float expandedHeight, float appearFraction) {
@@ -258,8 +276,10 @@ public class NotificationRoundnessManager implements Dumpable {
mNotifLogger.onSectionCornersUpdated(sections, anyChanged);
}
- private boolean handleRemovedOldViews(NotificationSection[] sections,
- ExpandableView[] oldViews, boolean first) {
+ private boolean handleRemovedOldViews(
+ NotificationSection[] sections,
+ ExpandableView[] oldViews,
+ boolean first) {
boolean anyChanged = false;
for (ExpandableView oldView : oldViews) {
if (oldView != null) {
@@ -289,8 +309,10 @@ public class NotificationRoundnessManager implements Dumpable {
return anyChanged;
}
- private boolean handleAddedNewViews(NotificationSection[] sections,
- ExpandableView[] oldViews, boolean first) {
+ private boolean handleAddedNewViews(
+ NotificationSection[] sections,
+ ExpandableView[] oldViews,
+ boolean first) {
boolean anyChanged = false;
for (NotificationSection section : sections) {
ExpandableView newView =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
index cb7dfe87f7fb..b61c55edadcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.statusbar.notification.stack
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationSectionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
private const val TAG = "NotifSections"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 91a28139c775..a1b77acb9a5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -19,11 +19,10 @@ import android.annotation.ColorInt
import android.util.Log
import android.view.View
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.media.KeyguardMediaController
+import com.android.systemui.media.controls.ui.KeyguardMediaController
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
-import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager
import com.android.systemui.statusbar.notification.dagger.AlertingHeader
import com.android.systemui.statusbar.notification.dagger.IncomingHeader
import com.android.systemui.statusbar.notification.dagger.PeopleHeader
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 55c577f1ea39..df705c5afeef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -255,7 +255,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private boolean mClearAllInProgress;
private FooterClearAllListener mFooterClearAllListener;
private boolean mFlingAfterUpEvent;
-
/**
* Was the scroller scrolled to the top when the down motion was observed?
*/
@@ -1189,7 +1188,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return;
}
for (int i = 0; i < getChildCount(); i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
+ ExpandableView child = getChildAtIndex(i);
if (mChildrenToAddAnimated.contains(child)) {
final int startingPosition = getPositionInLinearLayout(child);
final int childHeight = getIntrinsicHeight(child) + mPaddingBetweenElements;
@@ -1659,7 +1658,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// find the view under the pointer, accounting for GONE views
final int count = getChildCount();
for (int childIdx = 0; childIdx < count; childIdx++) {
- ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
+ ExpandableView slidingChild = getChildAtIndex(childIdx);
if (slidingChild.getVisibility() != VISIBLE
|| (ignoreDecors && slidingChild instanceof StackScrollerDecorView)) {
continue;
@@ -1692,6 +1691,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return null;
}
+ private ExpandableView getChildAtIndex(int index) {
+ return (ExpandableView) getChildAt(index);
+ }
+
public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
getLocationOnScreen(mTempInt2);
return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
@@ -2277,7 +2280,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
int childCount = getChildCount();
int count = 0;
for (int i = 0; i < childCount; i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
+ ExpandableView child = getChildAtIndex(i);
if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) {
count++;
}
@@ -2497,7 +2500,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private ExpandableView getLastChildWithBackground() {
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
- ExpandableView child = (ExpandableView) getChildAt(i);
+ ExpandableView child = getChildAtIndex(i);
if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView)
&& child != mShelf) {
return child;
@@ -2510,7 +2513,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private ExpandableView getFirstChildWithBackground() {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
+ ExpandableView child = getChildAtIndex(i);
if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView)
&& child != mShelf) {
return child;
@@ -2524,7 +2527,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
ArrayList<ExpandableView> children = new ArrayList<>();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
+ ExpandableView child = getChildAtIndex(i);
if (child.getVisibility() != View.GONE
&& !(child instanceof StackScrollerDecorView)
&& child != mShelf) {
@@ -2883,7 +2886,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
int position = 0;
for (int i = 0; i < getChildCount(); i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
+ ExpandableView child = getChildAtIndex(i);
boolean notGone = child.getVisibility() != View.GONE;
if (notGone && !child.hasNoContentHeight()) {
if (position != 0) {
@@ -2937,7 +2940,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
mAmbientState.setLastVisibleBackgroundChild(lastChild);
// TODO: Refactor SectionManager and put the RoundnessManager there.
- mController.getNoticationRoundessManager().updateRoundedChildren(mSections);
+ mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
mAnimateBottomOnLayout = false;
invalidate();
}
@@ -3969,7 +3972,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private void clearUserLockedViews() {
for (int i = 0; i < getChildCount(); i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
+ ExpandableView child = getChildAtIndex(i);
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
row.setUserLocked(false);
@@ -3982,7 +3985,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// lets make sure nothing is transient anymore
clearTemporaryViewsInGroup(this);
for (int i = 0; i < getChildCount(); i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
+ ExpandableView child = getChildAtIndex(i);
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
clearTemporaryViewsInGroup(row.getChildrenContainer());
@@ -4020,8 +4023,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
setOwnScrollY(0);
}
+ @VisibleForTesting
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
- private void setIsExpanded(boolean isExpanded) {
+ void setIsExpanded(boolean isExpanded) {
boolean changed = isExpanded != mIsExpanded;
mIsExpanded = isExpanded;
mStackScrollAlgorithm.setIsExpanded(isExpanded);
@@ -4230,7 +4234,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (hideSensitive != mAmbientState.isHideSensitive()) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
- ExpandableView v = (ExpandableView) getChildAt(i);
+ ExpandableView v = getChildAtIndex(i);
v.setHideSensitiveForIntrinsicHeight(hideSensitive);
}
mAmbientState.setHideSensitive(hideSensitive);
@@ -4265,7 +4269,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private void applyCurrentState() {
int numChildren = getChildCount();
for (int i = 0; i < numChildren; i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
+ ExpandableView child = getChildAtIndex(i);
child.applyViewState();
}
@@ -4285,7 +4289,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// Lefts first sort by Z difference
for (int i = 0; i < getChildCount(); i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
+ ExpandableView child = getChildAtIndex(i);
if (child.getVisibility() != GONE) {
mTmpSortedChildren.add(child);
}
@@ -4512,7 +4516,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void setClearAllInProgress(boolean clearAllInProgress) {
mClearAllInProgress = clearAllInProgress;
mAmbientState.setClearAllInProgress(clearAllInProgress);
- mController.getNoticationRoundessManager().setClearAllInProgress(clearAllInProgress);
+ mController.getNotificationRoundnessManager().setClearAllInProgress(clearAllInProgress);
}
boolean getClearAllInProgress() {
@@ -4555,7 +4559,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
final int count = getChildCount();
float max = 0;
for (int childIdx = 0; childIdx < count; childIdx++) {
- ExpandableView child = (ExpandableView) getChildAt(childIdx);
+ ExpandableView child = getChildAtIndex(childIdx);
if (child.getVisibility() == GONE) {
continue;
}
@@ -4586,7 +4590,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public boolean isBelowLastNotification(float touchX, float touchY) {
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
- ExpandableView child = (ExpandableView) getChildAt(i);
+ ExpandableView child = getChildAtIndex(i);
if (child.getVisibility() != View.GONE) {
float childTop = child.getY();
if (childTop > touchY) {
@@ -4842,13 +4846,21 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
+ @VisibleForTesting
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
- private void setOwnScrollY(int ownScrollY) {
+ void setOwnScrollY(int ownScrollY) {
setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */);
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) {
+ // Avoid Flicking during clear all
+ // when the shade finishes closing, onExpansionStopped will call
+ // resetScrollPosition to setOwnScrollY to 0
+ if (mAmbientState.isClosing()) {
+ return;
+ }
+
if (ownScrollY != mOwnScrollY) {
// We still want to call the normal scrolled changed for accessibility reasons
onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
@@ -5044,7 +5056,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
pw.println();
for (int i = 0; i < childCount; i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
+ ExpandableView child = getChildAtIndex(i);
child.dump(pw, args);
pw.println();
}
@@ -5333,7 +5345,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
float wakeUplocation = -1f;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
- ExpandableView view = (ExpandableView) getChildAt(i);
+ ExpandableView view = getChildAtIndex(i);
if (view.getVisibility() == View.GONE) {
continue;
}
@@ -5372,7 +5384,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void setController(
NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
mController = notificationStackScrollLayoutController;
- mController.getNoticationRoundessManager().setAnimatedChildren(mChildrenToAddAnimated);
+ mController.getNotificationRoundnessManager().setAnimatedChildren(mChildrenToAddAnimated);
}
void addSwipedOutView(View v) {
@@ -5383,31 +5395,22 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (!(viewSwiped instanceof ExpandableNotificationRow)) {
return;
}
- final int indexOfSwipedView = indexOfChild(viewSwiped);
- if (indexOfSwipedView < 0) {
- return;
- }
mSectionsManager.updateFirstAndLastViewsForAllSections(
- mSections, getChildrenWithBackground());
- View viewBefore = null;
- if (indexOfSwipedView > 0) {
- viewBefore = getChildAt(indexOfSwipedView - 1);
- if (mSectionsManager.beginsSection(viewSwiped, viewBefore)) {
- viewBefore = null;
- }
- }
- View viewAfter = null;
- if (indexOfSwipedView < getChildCount()) {
- viewAfter = getChildAt(indexOfSwipedView + 1);
- if (mSectionsManager.beginsSection(viewAfter, viewSwiped)) {
- viewAfter = null;
- }
- }
- mController.getNoticationRoundessManager()
+ mSections,
+ getChildrenWithBackground()
+ );
+
+ RoundableTargets targets = mController.getNotificationTargetsHelper().findRoundableTargets(
+ (ExpandableNotificationRow) viewSwiped,
+ this,
+ mSectionsManager
+ );
+
+ mController.getNotificationRoundnessManager()
.setViewsAffectedBySwipe(
- (ExpandableView) viewBefore,
- (ExpandableView) viewSwiped,
- (ExpandableView) viewAfter);
+ targets.getBefore(),
+ targets.getSwiped(),
+ targets.getAfter());
updateFirstAndLastBackgroundViews();
requestDisallowInterceptTouchEvent(true);
@@ -5418,7 +5421,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
void onSwipeEnd() {
updateFirstAndLastBackgroundViews();
- mController.getNoticationRoundessManager()
+ mController.getNotificationRoundnessManager()
.setViewsAffectedBySwipe(null, null, null);
// Round bottom corners for notification right before shelf.
mShelf.updateAppearance();
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 5c09d618403d..e13378269ba6 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
@@ -63,7 +63,7 @@ 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.media.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
@@ -180,6 +180,7 @@ public class NotificationStackScrollLayoutController {
private int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private final FeatureFlags mFeatureFlags;
+ private final NotificationTargetsHelper mNotificationTargetsHelper;
private View mLongPressedView;
@@ -642,7 +643,8 @@ public class NotificationStackScrollLayoutController {
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ NotificationTargetsHelper notificationTargetsHelper) {
mStackStateLogger = stackLogger;
mLogger = logger;
mAllowLongPress = allowLongPress;
@@ -679,6 +681,7 @@ public class NotificationStackScrollLayoutController {
mRemoteInputManager = remoteInputManager;
mShadeController = shadeController;
mFeatureFlags = featureFlags;
+ mNotificationTargetsHelper = notificationTargetsHelper;
updateResources();
}
@@ -1380,7 +1383,7 @@ public class NotificationStackScrollLayoutController {
return mView.calculateGapHeight(previousView, child, count);
}
- NotificationRoundnessManager getNoticationRoundessManager() {
+ NotificationRoundnessManager getNotificationRoundnessManager() {
return mNotificationRoundnessManager;
}
@@ -1537,6 +1540,10 @@ public class NotificationStackScrollLayoutController {
mNotificationActivityStarter = activityStarter;
}
+ public NotificationTargetsHelper getNotificationTargetsHelper() {
+ return mNotificationTargetsHelper;
+ }
+
/**
* Enum for UiEvent logged from this class
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 5f79c0e3913a..4c52db7f8732 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.statusbar.notification.stack
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
new file mode 100644
index 000000000000..991a14bb9c2a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
@@ -0,0 +1,100 @@
+package com.android.systemui.statusbar.notification.stack
+
+import androidx.core.view.children
+import androidx.core.view.isVisible
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.Roundable
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import javax.inject.Inject
+
+/**
+ * Utility class that helps us find the targets of an animation, often used to find the notification
+ * ([Roundable]) above and below the current one (see [findRoundableTargets]).
+ */
+@SysUISingleton
+class NotificationTargetsHelper
+@Inject
+constructor(
+ featureFlags: FeatureFlags,
+) {
+ private val isNotificationGroupCornerEnabled =
+ featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER)
+
+ /**
+ * This method looks for views that can be rounded (and implement [Roundable]) during a
+ * notification swipe.
+ * @return The [Roundable] targets above/below the [viewSwiped] (if available). The
+ * [RoundableTargets.before] and [RoundableTargets.after] parameters can be `null` if there is
+ * no above/below notification or the notification is not part of the same section.
+ */
+ fun findRoundableTargets(
+ viewSwiped: ExpandableNotificationRow,
+ stackScrollLayout: NotificationStackScrollLayout,
+ sectionsManager: NotificationSectionsManager,
+ ): RoundableTargets {
+ val viewBefore: Roundable?
+ val viewAfter: Roundable?
+
+ val notificationParent = viewSwiped.notificationParent
+ val childrenContainer = notificationParent?.childrenContainer
+ val visibleStackChildren =
+ stackScrollLayout.children
+ .filterIsInstance<ExpandableView>()
+ .filter { it.isVisible }
+ .toList()
+ if (notificationParent != null && childrenContainer != null) {
+ // We are inside a notification group
+
+ if (!isNotificationGroupCornerEnabled) {
+ return RoundableTargets(null, null, null)
+ }
+
+ val visibleGroupChildren = childrenContainer.attachedChildren.filter { it.isVisible }
+ val indexOfParentSwipedView = visibleGroupChildren.indexOf(viewSwiped)
+
+ viewBefore =
+ visibleGroupChildren.getOrNull(indexOfParentSwipedView - 1)
+ ?: childrenContainer.notificationHeaderWrapper
+
+ viewAfter =
+ visibleGroupChildren.getOrNull(indexOfParentSwipedView + 1)
+ ?: visibleStackChildren.indexOf(notificationParent).let {
+ visibleStackChildren.getOrNull(it + 1)
+ }
+ } else {
+ // Assumption: we are inside the NotificationStackScrollLayout
+
+ val indexOfSwipedView = visibleStackChildren.indexOf(viewSwiped)
+
+ viewBefore =
+ visibleStackChildren.getOrNull(indexOfSwipedView - 1)?.takeIf {
+ !sectionsManager.beginsSection(viewSwiped, it)
+ }
+
+ viewAfter =
+ visibleStackChildren.getOrNull(indexOfSwipedView + 1)?.takeIf {
+ !sectionsManager.beginsSection(it, viewSwiped)
+ }
+ }
+
+ return RoundableTargets(
+ before = viewBefore,
+ swiped = viewSwiped,
+ after = viewAfter,
+ )
+ }
+}
+
+/**
+ * This object contains targets above/below the [swiped] (if available). The [before] and [after]
+ * parameters can be `null` if there is no above/below notification or the notification is not part
+ * of the same section.
+ */
+data class RoundableTargets(
+ val before: Roundable?,
+ val swiped: ExpandableNotificationRow?,
+ val after: Roundable?,
+)
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 0502159f46cd..eea1d9118fb7 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
@@ -31,6 +31,7 @@ import com.android.systemui.R;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -804,7 +805,7 @@ public class StackScrollAlgorithm {
row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius);
- row.setBottomRoundness(roundness, /* animate= */ false);
+ row.requestBottomRoundness(roundness, /* animate = */ false, SourceType.OnScroll);
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index cb4a0884fea4..f5de678a8536 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.statusbar.notification.stack
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index fa7bfaeb6c4d..169c90780926 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -262,8 +262,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
@Override
void startActivity(Intent intent, boolean dismissShade, Callback callback);
- void setQsExpanded(boolean expanded);
-
boolean isWakeUpComingFromTouch();
boolean isFalsingThresholdNeeded();
@@ -455,6 +453,9 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
void collapseShade();
+ /** Collapse the shade, but conditional on a flag specific to the trigger of a bugreport. */
+ void collapseShadeForBugreport();
+
int getWakefulnessState();
boolean isScreenFullyOff();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 3df7273f17ed..db8aac95e20c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -91,7 +91,6 @@ import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
-import android.util.Slog;
import android.view.Display;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowManager;
@@ -886,6 +885,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
}
+ // Do not restart System UI when the bugreport flag changes.
+ mFeatureFlags.addListener(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, event -> {
+ event.requestNoRestart();
+ });
+
mStatusBarSignalPolicy.init();
mKeyguardIndicationController.init();
@@ -1137,7 +1141,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// TODO: Deal with the ugliness that comes from having some of the status bar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
- mNotificationLogger.setUpWithContainer(mNotifListContainer);
mNotificationIconAreaController.setupShelf(mNotificationShelfController);
mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
mUserSwitcherController.init(mNotificationShadeWindowView);
@@ -1422,6 +1425,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
mNotificationsController.initialize(
+ this,
mPresenter,
mNotifListContainer,
mStackScrollerController.getNotifStackController(),
@@ -1791,18 +1795,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
@Override
- public void setQsExpanded(boolean expanded) {
- mNotificationShadeWindowController.setQsExpanded(expanded);
- mNotificationPanelViewController.setStatusAccessibilityImportance(expanded
- ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
- mNotificationPanelViewController.updateSystemUiStateFlags();
- if (getNavigationBarView() != null) {
- getNavigationBarView().onStatusBarPanelStateChanged();
- }
- }
-
- @Override
public boolean isWakeUpComingFromTouch() {
return mWakeUpComingFromTouch;
}
@@ -3324,7 +3316,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public boolean onBackPressed() {
if (mStatusBarKeyguardViewManager.canHandleBackPressed()) {
- mStatusBarKeyguardViewManager.onBackPressed(false /* unused */);
+ mStatusBarKeyguardViewManager.onBackPressed();
return true;
}
if (mNotificationPanelViewController.isQsCustomizing()) {
@@ -3581,6 +3573,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
}
+ @Override
+ public void collapseShadeForBugreport() {
+ if (!mFeatureFlags.isEnabled(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT)) {
+ collapseShade();
+ }
+ }
+
@VisibleForTesting
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
@@ -3812,8 +3811,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
if (mDevicePolicyManager.getCameraDisabled(null,
mLockscreenUserManager.getCurrentUserId())) {
return false;
- } else if (mStatusBarKeyguardViewManager == null
- || (isKeyguardShowing() && isKeyguardSecure())) {
+ } else if (isKeyguardShowing() && isKeyguardSecure()) {
// Check if the admin has disabled the camera specifically for the keyguard
return (mDevicePolicyManager.getKeyguardDisabledFeatures(null,
mLockscreenUserManager.getCurrentUserId())
@@ -4221,14 +4219,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public boolean isKeyguardSecure() {
- if (mStatusBarKeyguardViewManager == null) {
- // startKeyguard() hasn't been called yet, so we don't know.
- // Make sure anything that needs to know isKeyguardSecure() checks and re-checks this
- // value onVisibilityChanged().
- Slog.w(TAG, "isKeyguardSecure() called before startKeyguard(), returning false",
- new Throwable());
- return false;
- }
return mStatusBarKeyguardViewManager.isSecure();
}
@Override
@@ -4288,11 +4278,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
.Callback() {
@Override
public void onFinished() {
- if (mStatusBarKeyguardViewManager == null) {
- Log.w(TAG, "Tried to notify keyguard visibility when "
- + "mStatusBarKeyguardViewManager was null");
- return;
- }
if (mKeyguardStateController.isKeyguardFadingAway()) {
mStatusBarKeyguardViewManager.onKeyguardFadedAway();
}
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 d61c51e76e86..9bb4132490d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -91,6 +91,11 @@ public class KeyguardBouncer {
mBouncerPromptReason = mCallback.getBouncerPromptReason();
}
}
+
+ @Override
+ public void onNonStrongBiometricAllowedChanged(int userId) {
+ mBouncerPromptReason = mCallback.getBouncerPromptReason();
+ }
};
private final Runnable mRemoveViewRunnable = this::removeView;
private final KeyguardBypassController mKeyguardBypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index b987f6815000..b965ac97cc1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -26,6 +26,7 @@ import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm
@@ -95,14 +96,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
var bouncerShowing: Boolean = false
var altBouncerShowing: Boolean = false
var launchingAffordance: Boolean = false
- var qSExpanded = false
- set(value) {
- val changed = field != value
- field = value
- if (changed && !value) {
- maybePerformPendingUnlock()
- }
- }
+ var qsExpanded = false
@Inject
constructor(
@@ -111,6 +105,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
statusBarStateController: StatusBarStateController,
lockscreenUserManager: NotificationLockscreenUserManager,
keyguardStateController: KeyguardStateController,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
dumpManager: DumpManager
) {
this.mKeyguardStateController = keyguardStateController
@@ -132,6 +127,14 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
}
})
+ shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
+ val changed = qsExpanded != isQsExpanded
+ qsExpanded = isQsExpanded
+ if (changed && !isQsExpanded) {
+ maybePerformPendingUnlock()
+ }
+ }
+
val dismissByDefault = if (context.resources.getBoolean(
com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
tunerService.addTunable(object : TunerService.Tunable {
@@ -160,7 +163,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
): Boolean {
if (biometricSourceType == BiometricSourceType.FACE && bypassEnabled) {
val can = canBypass()
- if (!can && (isPulseExpanding || qSExpanded)) {
+ if (!can && (isPulseExpanding || qsExpanded)) {
pendingUnlock = PendingUnlock(biometricSourceType, isStrongBiometric)
}
return can
@@ -189,7 +192,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
altBouncerShowing -> true
statusBarStateController.state != StatusBarState.KEYGUARD -> false
launchingAffordance -> false
- isPulseExpanding || qSExpanded -> false
+ isPulseExpanding || qsExpanded -> false
else -> true
}
}
@@ -214,7 +217,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
pw.println(" altBouncerShowing: $altBouncerShowing")
pw.println(" isPulseExpanding: $isPulseExpanding")
pw.println(" launchingAffordance: $launchingAffordance")
- pw.println(" qSExpanded: $qSExpanded")
+ pw.println(" qSExpanded: $qsExpanded")
pw.println(" hasFaceFeature: $hasFaceFeature")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
index 02b235493715..4839fe6a7bef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
@@ -19,9 +19,9 @@ package com.android.systemui.statusbar.phone
import android.util.DisplayMetrics
import android.view.View
import com.android.internal.logging.nano.MetricsProto.MetricsEvent
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.LSShadeTransitionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
index 00c3e8fac0b4..5e2a7c8ca540 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -26,6 +26,7 @@ import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
@@ -67,7 +68,7 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
ActivityLaunchAnimator.Controller.fromView(v, null),
true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM);
} else {
- mUserSwitchDialogController.showDialog(v);
+ mUserSwitchDialogController.showDialog(v.getContext(), Expandable.fromView(v));
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 9f932238007a..84907686835b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -249,6 +249,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private Callback mCallback;
private boolean mWallpaperSupportsAmbientMode;
private boolean mScreenOn;
+ private boolean mTransparentScrimBackground;
// Scrim blanking callbacks
private Runnable mPendingFrameCallback;
@@ -341,6 +342,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mScrimBehind.setDefaultFocusHighlightEnabled(false);
mNotificationsScrim.setDefaultFocusHighlightEnabled(false);
mScrimInFront.setDefaultFocusHighlightEnabled(false);
+ mTransparentScrimBackground = notificationsScrim.getResources()
+ .getBoolean(R.bool.notification_scrim_transparent);
updateScrims();
mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
}
@@ -777,13 +780,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
if (mClipsQsScrim) {
- mBehindAlpha = 1;
- mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
+ mBehindAlpha = mTransparentScrimBackground ? 0 : 1;
+ mNotificationsAlpha =
+ mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
} else {
- mBehindAlpha = behindFraction * mDefaultScrimAlpha;
+ mBehindAlpha =
+ mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
// Delay fade-in of notification scrim a bit further, to coincide with the
// view fade in. Otherwise the empty panel can be quite jarring.
- mNotificationsAlpha = MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f,
+ mNotificationsAlpha = mTransparentScrimBackground
+ ? 0 : MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f,
mPanelExpansionFraction);
}
mBehindTint = mState.getBehindTint();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index ece7ee0ec98a..86f6ff850409 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -372,7 +372,7 @@ public interface StatusBarIconController {
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
- if (statusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ if (statusBarPipelineFlags.useNewMobileIcons()) {
// This starts the flow for the new pipeline, and will notify us of changes
mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
@@ -451,7 +451,7 @@ public interface StatusBarIconController {
@VisibleForTesting
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
final BaseStatusBarFrameLayout view;
- if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ if (mStatusBarPipelineFlags.useNewWifiIcon()) {
view = onCreateModernStatusBarWifiView(slot);
// When [ModernStatusBarWifiView] is created, it will automatically apply the
// correct view state so we don't need to call applyWifiState.
@@ -474,9 +474,9 @@ public interface StatusBarIconController {
String slot,
MobileIconState state
) {
- if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ if (mStatusBarPipelineFlags.useNewMobileIcons()) {
throw new IllegalStateException("Attempting to add a mobile icon while the new "
- + "pipeline is enabled is not supported");
+ + "icons are enabled is not supported");
}
// Use the `subId` field as a key to query for the correct context
@@ -497,7 +497,7 @@ public interface StatusBarIconController {
String slot,
int subId
) {
- if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
throw new IllegalStateException("Attempting to add a mobile icon using the new"
+ "pipeline, but the enabled flag is false.");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index e106b9e327ef..31e960ad7d69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -224,9 +224,9 @@ public class StatusBarIconControllerImpl implements Tunable,
*/
@Override
public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
- if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ if (mStatusBarPipelineFlags.useNewMobileIcons()) {
Log.d(TAG, "ignoring old pipeline callbacks, because the new "
- + "pipeline frontend is enabled");
+ + "icons are enabled");
return;
}
Slot mobileSlot = mStatusBarIconList.getSlot(slot);
@@ -249,9 +249,9 @@ public class StatusBarIconControllerImpl implements Tunable,
@Override
public void setNewMobileIconSubIds(List<Integer> subIds) {
- if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
Log.d(TAG, "ignoring new pipeline callback, "
- + "since the frontend is disabled");
+ + "since the new icons are disabled");
return;
}
Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index b9312c7fc40a..ccb5d8800ddb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -60,7 +60,6 @@ import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.BouncerView;
-import com.android.systemui.keyguard.data.BouncerViewDelegate;
import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.navigationbar.NavigationBarView;
@@ -136,7 +135,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController;
private final BouncerCallbackInteractor mBouncerCallbackInteractor;
private final BouncerInteractor mBouncerInteractor;
- private final BouncerViewDelegate mBouncerViewDelegate;
+ private final BouncerView mBouncerView;
private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@@ -203,7 +202,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (DEBUG) {
Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()");
}
- onBackPressed(false /* unused */);
+ onBackPressed();
};
private boolean mIsBackCallbackRegistered = false;
@@ -225,6 +224,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
protected CentralSurfaces mCentralSurfaces;
private NotificationPanelViewController mNotificationPanelViewController;
private BiometricUnlockController mBiometricUnlockController;
+ private boolean mCentralSurfacesRegistered;
private View mNotificationContainer;
@@ -326,7 +326,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mKeyguardSecurityModel = keyguardSecurityModel;
mBouncerCallbackInteractor = bouncerCallbackInteractor;
mBouncerInteractor = bouncerInteractor;
- mBouncerViewDelegate = bouncerView.getDelegate();
+ mBouncerView = bouncerView;
mFoldAodAnimationController = sysUIUnfoldComponent
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
@@ -356,6 +356,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mNotificationContainer = notificationContainer;
mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
centralSurfaces.getKeyguardMessageArea());
+ mCentralSurfacesRegistered = true;
registerListeners();
}
@@ -802,7 +803,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private void setDozing(boolean dozing) {
if (mDozing != dozing) {
mDozing = dozing;
- if (dozing || mBouncer.needsFullscreenBouncer()
+ if (dozing || needsFullscreenBouncer()
|| mKeyguardStateController.isOccluded()) {
reset(dozing /* hideBouncerWhenShowing */);
}
@@ -1080,27 +1081,20 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* @return whether a back press can be handled right now.
*/
public boolean canHandleBackPressed() {
- return mBouncer.isShowing();
+ return bouncerIsShowing();
}
/**
* Notifies this manager that the back button has been pressed.
*/
- // TODO(b/244635782): This "accept boolean and ignore it, and always return false" was done
- // to make it possible to check this in *and* allow merging to master,
- // where ArcStatusBarKeyguardViewManager inherits this class, and its
- // build will break if we change this interface.
- // So, overall, while this function refactors the behavior of onBackPressed,
- // (it now handles the back press, and no longer returns *whether* it did so)
- // its interface is not changing right now (but will, in a follow-up CL).
- public boolean onBackPressed(boolean ignored) {
+ public void onBackPressed() {
if (!canHandleBackPressed()) {
- return false;
+ return;
}
mCentralSurfaces.endAffordanceLaunch();
// The second condition is for SIM card locked bouncer
- if (bouncerIsScrimmed() && needsFullscreenBouncer()) {
+ if (bouncerIsScrimmed() && !needsFullscreenBouncer()) {
hideBouncer(false);
updateStates();
} else {
@@ -1116,7 +1110,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mNotificationPanelViewController.expandWithoutQs();
}
}
- return false;
+ return;
}
@Override
@@ -1130,8 +1124,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public boolean isFullscreenBouncer() {
- if (mBouncerViewDelegate != null) {
- return mBouncerViewDelegate.isFullScreenBouncer();
+ if (mBouncerView.getDelegate() != null) {
+ return mBouncerView.getDelegate().isFullScreenBouncer();
}
return mBouncer != null && mBouncer.isFullscreenBouncer();
}
@@ -1175,6 +1169,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
};
protected void updateStates() {
+ if (!mCentralSurfacesRegistered) {
+ return;
+ }
boolean showing = mKeyguardStateController.isShowing();
boolean occluded = mKeyguardStateController.isOccluded();
boolean bouncerShowing = bouncerIsShowing();
@@ -1287,15 +1284,15 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public boolean shouldDismissOnMenuPressed() {
- if (mBouncerViewDelegate != null) {
- return mBouncerViewDelegate.shouldDismissOnMenuPressed();
+ if (mBouncerView.getDelegate() != null) {
+ return mBouncerView.getDelegate().shouldDismissOnMenuPressed();
}
return mBouncer != null && mBouncer.shouldDismissOnMenuPressed();
}
public boolean interceptMediaKey(KeyEvent event) {
- if (mBouncerViewDelegate != null) {
- return mBouncerViewDelegate.interceptMediaKey(event);
+ if (mBouncerView.getDelegate() != null) {
+ return mBouncerView.getDelegate().interceptMediaKey(event);
}
return mBouncer != null && mBouncer.interceptMediaKey(event);
}
@@ -1304,8 +1301,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* @return true if the pre IME back event should be handled
*/
public boolean dispatchBackKeyEventPreIme() {
- if (mBouncerViewDelegate != null) {
- return mBouncerViewDelegate.dispatchBackKeyEventPreIme();
+ if (mBouncerView.getDelegate() != null) {
+ return mBouncerView.getDelegate().dispatchBackKeyEventPreIme();
}
return mBouncer != null && mBouncer.dispatchBackKeyEventPreIme();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index b9a1413ff791..81edff45c505 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -17,12 +17,12 @@
package com.android.systemui.statusbar.phone
import android.app.PendingIntent
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogLevel.ERROR
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.WARNING
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 64ca270558e8..a1e0c5067ef3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -179,7 +179,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
mLockscreenUserManager.setUpWithPresenter(this);
- mMediaManager.setUpWithPresenter(this);
mGutsManager.setUpWithPresenter(
this, mNotifListContainer, mOnSettingsClickListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
index 9ae378f34fc0..0e6b7f253bcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.phone.fragment
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.CollapsedSbFragmentLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.DisableFlagsLogger
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
index 0d52f46e571f..e498ae451400 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone.userswitcher
import android.content.Intent
import android.os.UserHandle
import android.view.View
+import com.android.systemui.animation.Expandable
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
@@ -75,7 +76,7 @@ class StatusBarUserSwitcherControllerImpl @Inject constructor(
null /* ActivityLaunchAnimator.Controller */,
true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM)
} else {
- userSwitcherDialogController.showDialog(view)
+ userSwitcherDialogController.showDialog(view.context, Expandable.fromView(view))
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 9b8b6434827e..06cd12dd1a0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -24,29 +24,19 @@ import javax.inject.Inject
/** All flagging methods related to the new status bar pipeline (see b/238425913). */
@SysUISingleton
class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {
- /**
- * Returns true if we should run the new pipeline backend.
- *
- * The new pipeline backend hooks up to all our external callbacks, logs those callback inputs,
- * and logs the output state.
- */
- fun isNewPipelineBackendEnabled(): Boolean =
- featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_BACKEND)
+ /** True if we should display the mobile icons using the new status bar data pipeline. */
+ fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
- /**
- * Returns true if we should run the new pipeline frontend *and* backend.
- *
- * The new pipeline frontend will use the outputted state from the new backend and will make the
- * correct changes to the UI.
- */
- fun isNewPipelineFrontendEnabled(): Boolean =
- isNewPipelineBackendEnabled() &&
- featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_FRONTEND)
+ /** True if we should display the wifi icon using the new status bar data pipeline. */
+ fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON)
+
+ // TODO(b/238425913): Add flags to only run the mobile backend or wifi backend so we get the
+ // logging without getting the UI effects.
/**
- * Returns true if we should apply some coloring to icons that were rendered with the new
+ * Returns true if we should apply some coloring to the wifi icon that was rendered with the new
* pipeline to help with debugging.
*/
- // For now, just always apply the debug coloring if we've enabled frontend rendering.
- fun useNewPipelineDebugColoring(): Boolean = isNewPipelineFrontendEnabled()
+ // For now, just always apply the debug coloring if we've enabled the new icon.
+ fun useWifiDebugColoring(): Boolean = useNewWifiIcon()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
new file mode 100644
index 000000000000..7aa5ee1389f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.airplane.data.repository
+
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings.Global
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.SettingObserver
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.util.settings.GlobalSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Provides data related to airplane mode.
+ *
+ * IMPORTANT: This is currently *not* used to render any airplane mode information anywhere. It is
+ * only used to help [com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel]
+ * determine what parts of the wifi icon view should be shown.
+ *
+ * TODO(b/238425913): Consider migrating the status bar airplane mode icon to use this repo.
+ */
+interface AirplaneModeRepository {
+ /** Observable for whether the device is currently in airplane mode. */
+ val isAirplaneMode: StateFlow<Boolean>
+}
+
+@SysUISingleton
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class AirplaneModeRepositoryImpl
+@Inject
+constructor(
+ @Background private val bgHandler: Handler,
+ private val globalSettings: GlobalSettings,
+ logger: ConnectivityPipelineLogger,
+ @Application scope: CoroutineScope,
+) : AirplaneModeRepository {
+ // TODO(b/254848912): Replace this with a generic SettingObserver coroutine once we have it.
+ override val isAirplaneMode: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val observer =
+ object :
+ SettingObserver(
+ globalSettings,
+ bgHandler,
+ Global.AIRPLANE_MODE_ON,
+ UserHandle.USER_ALL
+ ) {
+ override fun handleValueChanged(value: Int, observedChange: Boolean) {
+ trySend(value == 1)
+ }
+ }
+
+ observer.isListening = true
+ trySend(observer.value == 1)
+ awaitClose { observer.isListening = false }
+ }
+ .distinctUntilChanged()
+ .logInputChange(logger, "isAirplaneMode")
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ // When the observer starts listening, the flow will emit the current value so the
+ // initialValue here is irrelevant.
+ initialValue = false,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
new file mode 100644
index 000000000000..3e9b2c2ae809
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.airplane.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * The business logic layer for airplane mode.
+ *
+ * IMPORTANT: This is currently *not* used to render any airplane mode information anywhere. See
+ * [AirplaneModeRepository] for more details.
+ */
+@SysUISingleton
+class AirplaneModeInteractor
+@Inject
+constructor(
+ airplaneModeRepository: AirplaneModeRepository,
+ connectivityRepository: ConnectivityRepository,
+) {
+ /** True if the device is currently in airplane mode. */
+ val isAirplaneMode: Flow<Boolean> = airplaneModeRepository.isAirplaneMode
+
+ /** True if we're configured to force-hide the airplane mode icon and false otherwise. */
+ val isForceHidden: Flow<Boolean> =
+ connectivityRepository.forceHiddenSlots.map { it.contains(ConnectivitySlot.AIRPLANE) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
new file mode 100644
index 000000000000..fe30c0169021
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Models the UI state for the status bar airplane mode icon.
+ *
+ * IMPORTANT: This is currently *not* used to render any airplane mode information anywhere. See
+ * [com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository] for
+ * more details.
+ */
+@SysUISingleton
+class AirplaneModeViewModel
+@Inject
+constructor(
+ interactor: AirplaneModeInteractor,
+ logger: ConnectivityPipelineLogger,
+ @Application private val scope: CoroutineScope,
+) {
+ /** True if the airplane mode icon is currently visible in the status bar. */
+ val isAirplaneModeIconVisible: StateFlow<Boolean> =
+ combine(interactor.isAirplaneMode, interactor.isForceHidden) {
+ isAirplaneMode,
+ isAirplaneIconForceHidden ->
+ isAirplaneMode && !isAirplaneIconForceHidden
+ }
+ .distinctUntilChanged()
+ .logOutputChange(logger, "isAirplaneModeIconVisible")
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 06d554232565..2aaa085645e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline.dagger
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
@@ -30,6 +32,9 @@ import dagger.Module
@Module
abstract class StatusBarPipelineModule {
@Binds
+ abstract fun airplaneModeRepository(impl: AirplaneModeRepositoryImpl): AirplaneModeRepository
+
+ @Binds
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index dbb1aa54d8ee..d3cf32fb44ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -18,10 +18,10 @@ package com.android.systemui.statusbar.pipeline.shared
import android.net.Network
import android.net.NetworkCapabilities
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.StatusBarConnectivityLog
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 681cf7254ae7..93448c1dee0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -39,7 +39,6 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import java.util.concurrent.Executor
@@ -64,6 +63,9 @@ interface WifiRepository {
/** Observable for the current wifi enabled status. */
val isWifiEnabled: StateFlow<Boolean>
+ /** Observable for the current wifi default status. */
+ val isWifiDefault: StateFlow<Boolean>
+
/** Observable for the current wifi network. */
val wifiNetwork: StateFlow<WifiNetworkModel>
@@ -103,7 +105,7 @@ class WifiRepositoryImpl @Inject constructor(
merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
.mapLatest { wifiManager.isWifiEnabled }
.distinctUntilChanged()
- .logOutputChange(logger, "enabled")
+ .logInputChange(logger, "enabled")
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
@@ -111,6 +113,39 @@ class WifiRepositoryImpl @Inject constructor(
)
}
+ override val isWifiDefault: StateFlow<Boolean> = conflatedCallbackFlow {
+ // Note: This callback doesn't do any logging because we already log every network change
+ // in the [wifiNetwork] callback.
+ val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
+ // This method will always be called immediately after the network becomes the
+ // default, in addition to any time the capabilities change while the network is
+ // the default.
+ // If this network contains valid wifi info, then wifi is the default network.
+ val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
+ trySend(wifiInfo != null)
+ }
+
+ override fun onLost(network: Network) {
+ // The system no longer has a default network, so wifi is definitely not default.
+ trySend(false)
+ }
+ }
+
+ connectivityManager.registerDefaultNetworkCallback(callback)
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .distinctUntilChanged()
+ .logInputChange(logger, "isWifiDefault")
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false
+ )
+
override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow {
var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 04b17ed2924a..3a3e611de96a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -59,6 +59,9 @@ class WifiInteractor @Inject constructor(
/** Our current enabled status. */
val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
+ /** Our current default status. */
+ val isDefault: Flow<Boolean> = wifiRepository.isWifiDefault
+
/** Our current wifi network. See [WifiNetworkModel]. */
val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 273be63eb8a2..25537b948517 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -91,6 +91,7 @@ object WifiViewBinder {
val activityInView = view.requireViewById<ImageView>(R.id.wifi_in)
val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out)
val activityContainerView = view.requireViewById<View>(R.id.inout_container)
+ val airplaneSpacer = view.requireViewById<View>(R.id.wifi_airplane_spacer)
view.isVisible = true
iconView.isVisible = true
@@ -142,6 +143,12 @@ object WifiViewBinder {
activityContainerView.isVisible = visible
}
}
+
+ launch {
+ viewModel.isAirplaneSpacerVisible.distinctUntilChanged().collect { visible ->
+ airplaneSpacer.isVisible = visible
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
index 40f948f9ee6c..95ab251422b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -32,6 +32,7 @@ class HomeWifiViewModel(
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
+ isAirplaneSpacerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
@@ -40,4 +41,5 @@ class HomeWifiViewModel(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
index 9642ac42972e..86535d63f84f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
@@ -29,6 +29,7 @@ class KeyguardWifiViewModel(
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
+ isAirplaneSpacerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
@@ -37,4 +38,5 @@ class KeyguardWifiViewModel(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index e23f8c7e97e0..7cbdf5dbdf2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -44,11 +44,14 @@ abstract class LocationBasedWifiViewModel(
/** True if the activity container view should be visible. */
val isActivityContainerVisible: Flow<Boolean>,
+
+ /** True if the airplane spacer view should be visible. */
+ val isAirplaneSpacerVisible: Flow<Boolean>,
) {
/** The color that should be used to tint the icon. */
val tint: Flow<Int> =
flowOf(
- if (statusBarPipelineFlags.useNewPipelineDebugColoring()) {
+ if (statusBarPipelineFlags.useWifiDebugColoring()) {
debugTint
} else {
DEFAULT_TINT
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
index 0ddf90e21872..fd54c5f5062e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
@@ -29,6 +29,7 @@ class QsWifiViewModel(
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
+ isAirplaneSpacerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
@@ -37,4 +38,5 @@ class QsWifiViewModel(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index ebbd77b72014..89b96b7bc75d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -31,6 +31,7 @@ import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
@@ -66,6 +67,7 @@ import kotlinx.coroutines.flow.stateIn
class WifiViewModel
@Inject
constructor(
+ airplaneModeViewModel: AirplaneModeViewModel,
connectivityConstants: ConnectivityConstants,
private val context: Context,
logger: ConnectivityPipelineLogger,
@@ -124,9 +126,10 @@ constructor(
private val wifiIcon: StateFlow<Icon.Resource?> =
combine(
interactor.isEnabled,
+ interactor.isDefault,
interactor.isForceHidden,
interactor.wifiNetwork,
- ) { isEnabled, isForceHidden, wifiNetwork ->
+ ) { isEnabled, isDefault, isForceHidden, wifiNetwork ->
if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
return@combine null
}
@@ -135,6 +138,7 @@ constructor(
val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
return@combine when {
+ isDefault -> icon
wifiConstants.alwaysShowIconIfEnabled -> icon
!connectivityConstants.hasDataCapabilities -> icon
wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
@@ -175,6 +179,12 @@ constructor(
}
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+ // TODO(b/238425913): It isn't ideal for the wifi icon to need to know about whether the
+ // airplane icon is visible. Instead, we should have a parent StatusBarSystemIconsViewModel
+ // that appropriately knows about both icons and sets the padding appropriately.
+ private val isAirplaneSpacerVisible: Flow<Boolean> =
+ airplaneModeViewModel.isAirplaneModeIconVisible
+
/** A view model for the status bar on the home screen. */
val home: HomeWifiViewModel =
HomeWifiViewModel(
@@ -183,6 +193,7 @@ constructor(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
/** A view model for the status bar on keyguard. */
@@ -193,6 +204,7 @@ constructor(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
/** A view model for the status bar in quick settings. */
@@ -203,6 +215,7 @@ constructor(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index 2f0ebf752a23..28a9b97b8ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -43,11 +43,7 @@ protected constructor(
}
override fun getCount(): Int {
- return if (controller.isKeyguardShowing) {
- users.count { !it.isRestricted }
- } else {
- users.size
- }
+ return users.size
}
override fun getItem(position: Int): UserRecord {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index d7c81af53d8b..df1e80b78c9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -16,10 +16,10 @@
package com.android.systemui.statusbar.policy
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.VERBOSE
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index dc73d1f007c6..f63d65246d9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -36,6 +36,7 @@ import com.android.keyguard.KeyguardVisibilityHelper;
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.R;
+import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -190,7 +191,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout>
mUiEventLogger.log(
LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
- mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground);
+ mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(),
+ Expandable.fromView(mUserAvatarViewWithBackground));
});
mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index da6d455fbb97..dd400b3fc0ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -47,6 +47,7 @@ import android.view.OnReceiveContentListener;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsController;
@@ -61,6 +62,8 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -88,6 +91,7 @@ import java.util.function.Consumer;
*/
public class RemoteInputView extends LinearLayout implements View.OnClickListener {
+ private static final boolean DEBUG = false;
private static final String TAG = "RemoteInput";
// A marker object that let's us easily find views of this class.
@@ -124,6 +128,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
// TODO(b/193539698): remove this; views shouldn't have access to their controller, and places
// that need the controller shouldn't have access to the view
private RemoteInputViewController mViewController;
+ private ViewRootImpl mTestableViewRootImpl;
/**
* Enum for logged notification remote input UiEvents.
@@ -430,10 +435,20 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
}
}
+ @VisibleForTesting
+ protected void setViewRootImpl(ViewRootImpl viewRoot) {
+ mTestableViewRootImpl = viewRoot;
+ }
+
+ @VisibleForTesting
+ protected void setEditTextReferenceToSelf() {
+ mEditText.mRemoteInputView = this;
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mEditText.mRemoteInputView = this;
+ setEditTextReferenceToSelf();
mEditText.setOnEditorActionListener(mEditorActionHandler);
mEditText.addTextChangedListener(mTextWatcher);
if (mEntry.getRow().isChangingPosition()) {
@@ -457,7 +472,50 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
}
@Override
+ public ViewRootImpl getViewRootImpl() {
+ if (mTestableViewRootImpl != null) {
+ return mTestableViewRootImpl;
+ }
+ return super.getViewRootImpl();
+ }
+
+ private void registerBackCallback() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot == null) {
+ if (DEBUG) {
+ Log.d(TAG, "ViewRoot was null, NOT registering Predictive Back callback");
+ }
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "registering Predictive Back callback");
+ }
+ viewRoot.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_OVERLAY, mEditText.mOnBackInvokedCallback);
+ }
+
+ private void unregisterBackCallback() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot == null) {
+ if (DEBUG) {
+ Log.d(TAG, "ViewRoot was null, NOT unregistering Predictive Back callback");
+ }
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "unregistering Predictive Back callback");
+ }
+ viewRoot.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(
+ mEditText.mOnBackInvokedCallback);
+ }
+
+ @Override
public void onVisibilityAggregated(boolean isVisible) {
+ if (isVisible) {
+ registerBackCallback();
+ } else {
+ unregisterBackCallback();
+ }
super.onVisibilityAggregated(isVisible);
mEditText.setEnabled(isVisible && !mSending);
}
@@ -822,10 +880,21 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
return super.onKeyDown(keyCode, event);
}
+ private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+ if (DEBUG) {
+ Log.d(TAG, "Predictive Back Callback dispatched");
+ }
+ respondToKeycodeBack();
+ };
+
+ private void respondToKeycodeBack() {
+ defocusIfNeeded(true /* animate */);
+ }
+
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
- defocusIfNeeded(true /* animate */);
+ respondToKeycodeBack();
return true;
}
return super.onKeyUp(keyCode, event);
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 5cbdf7c43a12..f0a50de02b3a 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -17,7 +17,6 @@
package com.android.systemui.temporarydisplay
import android.annotation.LayoutRes
-import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.Rect
@@ -67,11 +66,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
* 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
internal val commonWindowLayoutParams = WindowManager.LayoutParams().apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
- type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
+ type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = windowTitle
@@ -131,7 +129,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
)
cancelViewTimeout?.run()
cancelViewTimeout = mainExecutor.executeDelayed(
- { removeView(TemporaryDisplayRemovalReason.REASON_TIMEOUT) },
+ { removeView(REMOVAL_REASON_TIMEOUT) },
timeout.toLong()
)
}
@@ -175,9 +173,6 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
*/
fun removeView(removalReason: String) {
val currentDisplayInfo = displayInfo ?: return
- if (shouldIgnoreViewRemoval(currentDisplayInfo.info, removalReason)) {
- return
- }
val currentView = currentDisplayInfo.view
animateViewOut(currentView) { windowManager.removeView(currentView) }
@@ -193,13 +188,6 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
}
/**
- * Returns true if a view removal request should be ignored and false otherwise.
- *
- * Allows subclasses to keep the view visible for longer in certain circumstances.
- */
- open fun shouldIgnoreViewRemoval(info: T, removalReason: String): Boolean = false
-
- /**
* A method implemented by subclasses to update [currentView] based on [newInfo].
*/
abstract fun updateView(newInfo: T, currentView: ViewGroup)
@@ -236,10 +224,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
)
}
-object TemporaryDisplayRemovalReason {
- const val REASON_TIMEOUT = "TIMEOUT"
- const val REASON_SCREEN_TAP = "SCREEN_TAP"
-}
+private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
private data class IconInfo(
val iconName: String,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 606a11a84686..a7185cb18c40 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -16,8 +16,8 @@
package com.android.systemui.temporarydisplay
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
/** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
open class TemporaryViewLogger(
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
new file mode 100644
index 000000000000..cd7bd2dae8fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -0,0 +1,208 @@
+/*
+ * 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.temporarydisplay.chipbar
+
+import android.content.Context
+import android.graphics.Rect
+import android.os.PowerManager
+import android.view.Gravity
+import android.view.MotionEvent
+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.widget.CachingIconView
+import com.android.systemui.Gefingerpoken
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
+import javax.inject.Inject
+
+/**
+ * A coordinator for showing/hiding the chipbar.
+ *
+ * The chipbar is a UI element that displays on top of all content. It appears at the top of the
+ * screen and consists of an icon, one line of text, and an optional end icon or action. It will
+ * auto-dismiss after some amount of seconds. The user is *not* able to manually dismiss the
+ * chipbar.
+ *
+ * It should be only be used for critical and temporary information that the user *must* be aware
+ * of. In general, prefer using heads-up notifications, since they are dismissable and will remain
+ * in the list of notifications until the user dismisses them.
+ *
+ * Only one chipbar may be shown at a time.
+ * TODO(b/245610654): Should we just display whichever chipbar was most recently requested, or do we
+ * need to maintain a priority ordering?
+ *
+ * TODO(b/245610654): Remove all media-related items from this class so it's just for generic
+ * chipbars.
+ */
+@SysUISingleton
+open class ChipbarCoordinator @Inject constructor(
+ context: Context,
+ @MediaTttSenderLogger logger: MediaTttLogger,
+ windowManager: WindowManager,
+ @Main mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
+ private val falsingManager: FalsingManager,
+ private val falsingCollector: FalsingCollector,
+ private val viewUtil: ViewUtil,
+ private val vibratorHelper: VibratorHelper,
+) : TemporaryViewDisplayController<ChipbarInfo, MediaTttLogger>(
+ context,
+ logger,
+ windowManager,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ R.layout.chipbar,
+ MediaTttUtils.WINDOW_TITLE,
+ MediaTttUtils.WAKE_REASON,
+) {
+
+ private lateinit var parent: ChipbarRootView
+
+ override val windowLayoutParams = commonWindowLayoutParams.apply {
+ gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
+ }
+
+ override fun start() {}
+
+ override fun updateView(
+ newInfo: ChipbarInfo,
+ currentView: ViewGroup
+ ) {
+ // TODO(b/245610654): Adding logging here.
+
+ // Detect falsing touches on the chip.
+ parent = currentView.requireViewById(R.id.chipbar_root_view)
+ parent.touchHandler = object : Gefingerpoken {
+ override fun onTouchEvent(ev: MotionEvent?): Boolean {
+ falsingCollector.onTouchEvent(ev)
+ return false
+ }
+ }
+
+ // ---- Start icon ----
+ val iconView = currentView.requireViewById<CachingIconView>(R.id.start_icon)
+ IconViewBinder.bind(newInfo.startIcon, iconView)
+
+ // ---- Text ----
+ val textView = currentView.requireViewById<TextView>(R.id.text)
+ TextViewBinder.bind(textView, newInfo.text)
+ // Updates text view bounds to make sure it perfectly fits the new text
+ // (If the new text is smaller than the previous text) see b/253228632.
+ textView.requestLayout()
+
+ // ---- End item ----
+ // Loading
+ currentView.requireViewById<View>(R.id.loading).visibility =
+ (newInfo.endItem == ChipbarEndItem.Loading).visibleIfTrue()
+
+ // Error
+ currentView.requireViewById<View>(R.id.error).visibility =
+ (newInfo.endItem == ChipbarEndItem.Error).visibleIfTrue()
+
+ // Button
+ val buttonView = currentView.requireViewById<TextView>(R.id.end_button)
+ if (newInfo.endItem is ChipbarEndItem.Button) {
+ TextViewBinder.bind(buttonView, newInfo.endItem.text)
+
+ val onClickListener = View.OnClickListener { clickedView ->
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
+ newInfo.endItem.onClickListener.onClick(clickedView)
+ }
+
+ buttonView.setOnClickListener(onClickListener)
+ buttonView.visibility = View.VISIBLE
+ } else {
+ buttonView.visibility = View.GONE
+ }
+
+ // ---- Overall accessibility ----
+ currentView.requireViewById<ViewGroup>(
+ R.id.chipbar_inner
+ ).contentDescription =
+ "${newInfo.startIcon.contentDescription.loadContentDescription(context)} " +
+ "${newInfo.text.loadText(context)}"
+
+ // ---- Haptics ----
+ newInfo.vibrationEffect?.let {
+ vibratorHelper.vibrate(it)
+ }
+ }
+
+ override fun animateViewIn(view: ViewGroup) {
+ val chipInnerView = view.requireViewById<ViewGroup>(R.id.chipbar_inner)
+ ViewHierarchyAnimator.animateAddition(
+ chipInnerView,
+ ViewHierarchyAnimator.Hotspot.TOP,
+ Interpolators.EMPHASIZED_DECELERATE,
+ duration = ANIMATION_DURATION,
+ includeMargins = true,
+ includeFadeIn = true,
+ // We can only request focus once the animation finishes.
+ onAnimationEnd = { chipInnerView.requestAccessibilityFocus() },
+ )
+ }
+
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ ViewHierarchyAnimator.animateRemoval(
+ view.requireViewById<ViewGroup>(R.id.chipbar_inner),
+ ViewHierarchyAnimator.Hotspot.TOP,
+ Interpolators.EMPHASIZED_ACCELERATE,
+ ANIMATION_DURATION,
+ includeMargins = true,
+ onAnimationEnd,
+ )
+ }
+
+ override fun getTouchableRegion(view: View, outRect: Rect) {
+ viewUtil.setRectToViewWindowLocation(view, outRect)
+ }
+
+ private fun Boolean.visibleIfTrue(): Int {
+ return if (this) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+ }
+}
+
+const val SENDER_TAG = "MediaTapToTransferSender"
+private const val ANIMATION_DURATION = 500L
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
new file mode 100644
index 000000000000..57fde87114d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay.chipbar
+
+import android.os.VibrationEffect
+import android.view.View
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
+
+/**
+ * A container for all the state needed to display a chipbar via [ChipbarCoordinator].
+ *
+ * @property startIcon the icon to display at the start of the chipbar (on the left in LTR locales;
+ * on the right in RTL locales).
+ * @property text the text to display.
+ * @property endItem an optional end item to display at the end of the chipbar (on the right in LTR
+ * locales; on the left in RTL locales).
+ * @property vibrationEffect an optional vibration effect when the chipbar is displayed
+ */
+data class ChipbarInfo(
+ val startIcon: Icon,
+ val text: Text,
+ val endItem: ChipbarEndItem?,
+ val vibrationEffect: VibrationEffect? = null,
+) : TemporaryViewInfo
+
+/** The possible items to display at the end of the chipbar. */
+sealed class ChipbarEndItem {
+ /** A loading icon should be displayed. */
+ object Loading : ChipbarEndItem()
+
+ /** An error icon should be displayed. */
+ object Error : ChipbarEndItem()
+
+ /**
+ * A button with the provided [text] and [onClickListener] functionality should be displayed.
+ */
+ data class Button(val text: Text, val onClickListener: View.OnClickListener) : ChipbarEndItem()
+
+ // TODO(b/245610654): Add support for a generic icon.
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
index 3373159fba4e..edec420bc408 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.taptotransfer.sender
+package com.android.systemui.temporarydisplay.chipbar
import android.content.Context
import android.util.AttributeSet
@@ -22,8 +22,8 @@ import android.view.MotionEvent
import android.widget.FrameLayout
import com.android.systemui.Gefingerpoken
-/** A simple subclass that allows for observing touch events on chip. */
-class MediaTttChipRootView(
+/** A simple subclass that allows for observing touch events on chipbar. */
+class ChipbarRootView(
context: Context,
attrs: AttributeSet?
) : FrameLayout(context, attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 3d56f2317660..3ecb15b9d79c 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -79,6 +79,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
@@ -114,6 +115,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
private final SecureSettings mSecureSettings;
private final Executor mMainExecutor;
private final Handler mBgHandler;
+ private final boolean mIsMonochromaticEnabled;
private final Context mContext;
private final boolean mIsMonetEnabled;
private final UserTracker mUserTracker;
@@ -363,6 +365,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags,
@Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) {
mContext = context;
+ mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEMES);
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
mDeviceProvisionedController = deviceProvisionedController;
mBroadcastDispatcher = broadcastDispatcher;
@@ -665,8 +668,13 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
// Allow-list of Style objects that can be created from a setting string, i.e. can be
// used as a system-wide theme.
// - Content intentionally excluded, intended for media player, not system-wide
- List<Style> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, Style.TONAL_SPOT,
- Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT);
+ List<Style> validStyles = new ArrayList<>(Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ,
+ Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT));
+
+ if (mIsMonochromaticEnabled) {
+ validStyles.add(Style.MONOCHROMATIC);
+ }
+
Style style = mThemeStyle;
final String overlayPackageJson = mSecureSettings.getStringForUser(
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index 51541bd3032e..fda511433143 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -16,11 +16,11 @@
package com.android.systemui.toast
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
-import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.ToastLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogMessage
import javax.inject.Inject
private const val TAG = "ToastLog"
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 7f1195b78c77..7da2d47c1226 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -17,7 +17,8 @@
package com.android.systemui.user
import android.os.Bundle
-import android.view.View
+import android.view.WindowInsets.Type
+import android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
import androidx.activity.ComponentActivity
import androidx.lifecycle.ViewModelProvider
import com.android.systemui.R
@@ -38,10 +39,10 @@ constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.user_switcher_fullscreen)
- window.decorView.systemUiVisibility =
- (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
+ window.decorView.getWindowInsetsController().apply {
+ setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE)
+ hide(Type.systemBars())
+ }
val viewModel =
ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
UserSwitcherViewBinder.bind(
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
index ee785b62bd50..088cd93bdf7e 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -36,9 +36,7 @@ class UserSwitcherPopupMenu(
private var adapter: ListAdapter? = null
init {
- setBackgroundDrawable(
- res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme())
- )
+ setBackgroundDrawable(null)
setModal(false)
setOverlapAnchor(true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 919e699652bc..b16dc5403a57 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -220,7 +220,12 @@ constructor(
val result = withContext(backgroundDispatcher) { manager.aliveUsers }
if (result != null) {
- _userInfos.value = result.sortedBy { it.creationTime }
+ _userInfos.value =
+ result
+ // Users should be sorted by ascending creation time.
+ .sortedBy { it.creationTime }
+ // The guest user is always last, regardless of creation time.
+ .sortedBy { it.isGuest }
}
}
}
@@ -321,6 +326,7 @@ constructor(
return when {
isAddUser -> false
isAddSupervisedUser -> false
+ isManageUsers -> false
isGuest -> info != null
else -> true
}
@@ -346,6 +352,7 @@ constructor(
isAddUser -> UserActionModel.ADD_USER
isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
isGuest -> UserActionModel.ENTER_GUEST_MODE
+ isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
else -> error("Don't know how to convert to UserActionModel: $this")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index ba5a82a42d94..dda78aad54c6 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -236,18 +236,7 @@ constructor(
}
.flatMapLatest { isActionable ->
if (isActionable) {
- repository.actions.map { actions ->
- actions +
- if (actions.isNotEmpty()) {
- // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT
- // because that's a user switcher specific action that is
- // not known to the our data source or other features.
- listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
- } else {
- // If no actions, don't add the navigate action.
- emptyList()
- }
- }
+ repository.actions
} else {
// If not actionable it means that we're not allowed to show actions
// when
@@ -440,6 +429,7 @@ constructor(
isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
onExitGuestUser = this::exitGuestUser,
+ dialogShower = dialogShower,
)
)
return
@@ -454,6 +444,7 @@ constructor(
isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
onExitGuestUser = this::exitGuestUser,
+ dialogShower = dialogShower,
)
)
return
@@ -488,6 +479,7 @@ constructor(
userHandle = currentUser.userHandle,
isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
+ dialogShower = dialogShower,
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 08d7c5a26a25..177356e6b573 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -18,14 +18,18 @@
package com.android.systemui.user.domain.model
import android.os.UserHandle
+import com.android.systemui.qs.user.UserSwitchDialogController
/** Encapsulates a request to show a dialog. */
-sealed class ShowDialogRequestModel {
+sealed class ShowDialogRequestModel(
+ open val dialogShower: UserSwitchDialogController.DialogShower? = null,
+) {
data class ShowAddUserDialog(
val userHandle: UserHandle,
val isKeyguardShowing: Boolean,
val showEphemeralMessage: Boolean,
- ) : ShowDialogRequestModel()
+ override val dialogShower: UserSwitchDialogController.DialogShower?,
+ ) : ShowDialogRequestModel(dialogShower)
data class ShowUserCreationDialog(
val isGuest: Boolean,
@@ -37,5 +41,6 @@ sealed class ShowDialogRequestModel {
val isGuestEphemeral: Boolean,
val isKeyguardShowing: Boolean,
val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit,
- ) : ShowDialogRequestModel()
+ override val dialogShower: UserSwitchDialogController.DialogShower?,
+ ) : ShowDialogRequestModel(dialogShower)
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index 938417f9dbe3..968af59e6c45 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -18,12 +18,15 @@
package com.android.systemui.user.ui.binder
import android.content.Context
+import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.LinearLayout.SHOW_DIVIDER_MIDDLE
import android.widget.TextView
import androidx.constraintlayout.helper.widget.Flow as FlowWidget
import androidx.core.view.isVisible
@@ -36,6 +39,7 @@ import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.user.UserSwitcherPopupMenu
import com.android.systemui.user.UserSwitcherRootView
+import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.util.children
@@ -168,15 +172,10 @@ object UserSwitcherViewBinder {
onDismissed: () -> Unit,
): UserSwitcherPopupMenu {
return UserSwitcherPopupMenu(context).apply {
+ this.setDropDownGravity(Gravity.END)
this.anchorView = anchorView
setAdapter(adapter)
setOnDismissListener { onDismissed() }
- setOnItemClickListener { _, _, position, _ ->
- val itemPositionExcludingHeader = position - 1
- adapter.getItem(itemPositionExcludingHeader).onClicked()
- dismiss()
- }
-
show()
}
}
@@ -186,38 +185,67 @@ object UserSwitcherViewBinder {
private val layoutInflater: LayoutInflater,
) : BaseAdapter() {
- private val items = mutableListOf<UserActionViewModel>()
+ private var sections = listOf<List<UserActionViewModel>>()
override fun getCount(): Int {
- return items.size
+ return sections.size
}
- override fun getItem(position: Int): UserActionViewModel {
- return items[position]
+ override fun getItem(position: Int): List<UserActionViewModel> {
+ return sections[position]
}
override fun getItemId(position: Int): Long {
- return getItem(position).viewKey
+ return position.toLong()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- val view =
- convertView
- ?: layoutInflater.inflate(
+ val section = getItem(position)
+ val context = parent.context
+ val sectionView =
+ convertView as? LinearLayout
+ ?: LinearLayout(context, null).apply {
+ this.orientation = LinearLayout.VERTICAL
+ this.background =
+ parent.resources.getDrawable(
+ R.drawable.bouncer_user_switcher_popup_bg,
+ context.theme
+ )
+ this.showDividers = SHOW_DIVIDER_MIDDLE
+ this.dividerDrawable =
+ context.getDrawable(
+ R.drawable.fullscreen_userswitcher_menu_item_divider
+ )
+ }
+ sectionView.removeAllViewsInLayout()
+
+ for (viewModel in section) {
+ val view =
+ layoutInflater.inflate(
R.layout.user_switcher_fullscreen_popup_item,
- parent,
- false
+ /* parent= */ null
)
- val viewModel = getItem(position)
- view.requireViewById<ImageView>(R.id.icon).setImageResource(viewModel.iconResourceId)
- view.requireViewById<TextView>(R.id.text).text =
- view.resources.getString(viewModel.textResourceId)
- return view
+ view
+ .requireViewById<ImageView>(R.id.icon)
+ .setImageResource(viewModel.iconResourceId)
+ view.requireViewById<TextView>(R.id.text).text =
+ view.resources.getString(viewModel.textResourceId)
+ view.setOnClickListener { viewModel.onClicked() }
+ sectionView.addView(view)
+ }
+ return sectionView
}
fun setItems(items: List<UserActionViewModel>) {
- this.items.clear()
- this.items.addAll(items)
+ val primarySection =
+ items.filter {
+ it.viewKey != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong()
+ }
+ val secondarySection =
+ items.filter {
+ it.viewKey == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong()
+ }
+ this.sections = listOf(primarySection, secondarySection)
notifyDataSetChanged()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 91c592177d19..e9217209530b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -19,8 +19,10 @@ package com.android.systemui.user.ui.dialog
import android.app.Dialog
import android.content.Context
+import com.android.internal.jank.InteractionJankMonitor
import com.android.settingslib.users.UserCreatingDialog
import com.android.systemui.CoreStartable
+import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.SysUISingleton
@@ -30,6 +32,7 @@ import com.android.systemui.flags.Flags
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
@@ -41,19 +44,19 @@ import kotlinx.coroutines.launch
class UserSwitcherDialogCoordinator
@Inject
constructor(
- @Application private val context: Context,
- @Application private val applicationScope: CoroutineScope,
- private val falsingManager: FalsingManager,
- private val broadcastSender: BroadcastSender,
- private val dialogLaunchAnimator: DialogLaunchAnimator,
- private val interactor: UserInteractor,
- private val featureFlags: FeatureFlags,
+ @Application private val context: Lazy<Context>,
+ @Application private val applicationScope: Lazy<CoroutineScope>,
+ private val falsingManager: Lazy<FalsingManager>,
+ private val broadcastSender: Lazy<BroadcastSender>,
+ private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
+ private val interactor: Lazy<UserInteractor>,
+ private val featureFlags: Lazy<FeatureFlags>,
) : CoreStartable {
private var currentDialog: Dialog? = null
override fun start() {
- if (featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
+ if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
return
}
@@ -62,61 +65,87 @@ constructor(
}
private fun startHandlingDialogShowRequests() {
- applicationScope.launch {
- interactor.dialogShowRequests.filterNotNull().collect { request ->
+ applicationScope.get().launch {
+ interactor.get().dialogShowRequests.filterNotNull().collect { request ->
currentDialog?.let {
if (it.isShowing) {
it.cancel()
}
}
- currentDialog =
+ val (dialog, dialogCuj) =
when (request) {
is ShowDialogRequestModel.ShowAddUserDialog ->
- AddUserDialog(
- context = context,
- userHandle = request.userHandle,
- isKeyguardShowing = request.isKeyguardShowing,
- showEphemeralMessage = request.showEphemeralMessage,
- falsingManager = falsingManager,
- broadcastSender = broadcastSender,
- dialogLaunchAnimator = dialogLaunchAnimator,
+ Pair(
+ AddUserDialog(
+ context = context.get(),
+ userHandle = request.userHandle,
+ isKeyguardShowing = request.isKeyguardShowing,
+ showEphemeralMessage = request.showEphemeralMessage,
+ falsingManager = falsingManager.get(),
+ broadcastSender = broadcastSender.get(),
+ dialogLaunchAnimator = dialogLaunchAnimator.get(),
+ ),
+ DialogCuj(
+ InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+ INTERACTION_JANK_ADD_NEW_USER_TAG,
+ ),
)
is ShowDialogRequestModel.ShowUserCreationDialog ->
- UserCreatingDialog(
- context,
- request.isGuest,
+ Pair(
+ UserCreatingDialog(
+ context.get(),
+ request.isGuest,
+ ),
+ null,
)
is ShowDialogRequestModel.ShowExitGuestDialog ->
- ExitGuestDialog(
- context = context,
- guestUserId = request.guestUserId,
- isGuestEphemeral = request.isGuestEphemeral,
- targetUserId = request.targetUserId,
- isKeyguardShowing = request.isKeyguardShowing,
- falsingManager = falsingManager,
- dialogLaunchAnimator = dialogLaunchAnimator,
- onExitGuestUserListener = request.onExitGuestUser,
+ Pair(
+ ExitGuestDialog(
+ context = context.get(),
+ guestUserId = request.guestUserId,
+ isGuestEphemeral = request.isGuestEphemeral,
+ targetUserId = request.targetUserId,
+ isKeyguardShowing = request.isKeyguardShowing,
+ falsingManager = falsingManager.get(),
+ dialogLaunchAnimator = dialogLaunchAnimator.get(),
+ onExitGuestUserListener = request.onExitGuestUser,
+ ),
+ DialogCuj(
+ InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+ INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
+ ),
)
}
+ currentDialog = dialog
- currentDialog?.show()
- interactor.onDialogShown()
+ if (request.dialogShower != null && dialogCuj != null) {
+ request.dialogShower?.showDialog(dialog, dialogCuj)
+ } else {
+ dialog.show()
+ }
+
+ interactor.get().onDialogShown()
}
}
}
private fun startHandlingDialogDismissRequests() {
- applicationScope.launch {
- interactor.dialogDismissRequests.filterNotNull().collect {
+ applicationScope.get().launch {
+ interactor.get().dialogDismissRequests.filterNotNull().collect {
currentDialog?.let {
if (it.isShowing) {
it.cancel()
}
}
- interactor.onDialogDismissed()
+ interactor.get().onDialogDismissed()
}
}
}
+
+ companion object {
+ private const val INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user"
+ private const val INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 219dae29117f..d857e85bac53 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -62,17 +62,7 @@ private constructor(
val isMenuVisible: Flow<Boolean> = _isMenuVisible
/** The user action menu. */
val menu: Flow<List<UserActionViewModel>> =
- userInteractor.actions.map { actions ->
- if (isNewImpl && actions.isNotEmpty()) {
- // If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because that's a user
- // switcher specific action that is not known to the our data source or other
- // features.
- actions + listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
- } else {
- actions
- }
- .map { action -> toViewModel(action) }
- }
+ userInteractor.actions.map { actions -> actions.map { action -> toViewModel(action) } }
/** Whether the button to open the user action menu is visible. */
val isOpenMenuButtonVisible: Flow<Boolean> = menu.map { it.isNotEmpty() }
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
index ecb365f43e3f..2c317dd391c0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
@@ -172,10 +172,14 @@ public abstract class Condition implements CallbackController<Condition.Callback
return Boolean.TRUE.equals(mIsConditionMet);
}
- private boolean shouldLog() {
+ protected final boolean shouldLog() {
return Log.isLoggable(mTag, Log.DEBUG);
}
+ protected final String getTag() {
+ return mTag;
+ }
+
/**
* Callback that receives updates about whether the condition has been fulfilled.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index 4824f6744c6e..cb430ba454f0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -117,6 +117,7 @@ public class Monitor {
final SubscriptionState state = new SubscriptionState(subscription);
mExecutor.execute(() -> {
+ if (shouldLog()) Log.d(mTag, "adding subscription");
mSubscriptions.put(token, state);
// Add and associate conditions.
@@ -143,7 +144,7 @@ public class Monitor {
*/
public void removeSubscription(@NotNull Subscription.Token token) {
mExecutor.execute(() -> {
- if (shouldLog()) Log.d(mTag, "removing callback");
+ if (shouldLog()) Log.d(mTag, "removing subscription");
if (!mSubscriptions.containsKey(token)) {
Log.e(mTag, "subscription not present:" + token);
return;
diff --git a/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto b/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto
new file mode 100644
index 000000000000..b7166d96d401
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package com.android.systemui.util;
+
+option java_multiple_files = true;
+
+message ComponentNameProto {
+ string package_name = 1;
+ string class_name = 2;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index fbc6a582da2e..309f1681b964 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -22,6 +22,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_B
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -55,6 +56,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
+import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
@@ -111,6 +114,7 @@ public final class WMShell implements
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
private final Optional<FloatingTasks> mFloatingTasksOptional;
+ private final Optional<DesktopMode> mDesktopModeOptional;
private final CommandQueue mCommandQueue;
private final ConfigurationController mConfigurationController;
@@ -173,6 +177,7 @@ public final class WMShell implements
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
Optional<FloatingTasks> floatingTasksOptional,
+ Optional<DesktopMode> desktopMode,
CommandQueue commandQueue,
ConfigurationController configurationController,
KeyguardStateController keyguardStateController,
@@ -194,6 +199,7 @@ public final class WMShell implements
mPipOptional = pipOptional;
mSplitScreenOptional = splitScreenOptional;
mOneHandedOptional = oneHandedOptional;
+ mDesktopModeOptional = desktopMode;
mWakefulnessLifecycle = wakefulnessLifecycle;
mProtoTracer = protoTracer;
mUserTracker = userTracker;
@@ -219,6 +225,7 @@ public final class WMShell implements
mPipOptional.ifPresent(this::initPip);
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
+ mDesktopModeOptional.ifPresent(this::initDesktopMode);
}
@VisibleForTesting
@@ -326,6 +333,16 @@ public final class WMShell implements
});
}
+ void initDesktopMode(DesktopMode desktopMode) {
+ desktopMode.addListener(new DesktopModeTaskRepository.VisibleTasksListener() {
+ @Override
+ public void onVisibilityChanged(boolean hasFreeformTasks) {
+ mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, hasFreeformTasks)
+ .commitUpdate(DEFAULT_DISPLAY);
+ }
+ }, mSysUiMainExecutor);
+ }
+
@Override
public void writeToProto(SystemUiTraceProto proto) {
if (proto.wmShell == null) {
diff --git a/packages/SystemUI/tests/Android.bp b/packages/SystemUI/tests/Android.bp
new file mode 100644
index 000000000000..3c418ed49adc
--- /dev/null
+++ b/packages/SystemUI/tests/Android.bp
@@ -0,0 +1,50 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+ name: "SystemUITests",
+
+ dxflags: ["--multi-dex"],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+ static_libs: ["SystemUI-tests"],
+ compile_multilib: "both",
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
+ libs: [
+ "android.test.runner",
+ "telephony-common",
+ "android.test.base",
+ ],
+ aaptflags: [
+ "--extra-packages com.android.systemui",
+ ],
+
+ // sign this with platform cert, so this test is allowed to inject key events into
+ // UI it doesn't own. This is necessary to allow screenshots to be taken
+ certificate: "platform",
+
+ additional_manifests: ["AndroidManifest.xml"],
+ manifest: "AndroidManifest-base.xml",
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
deleted file mode 100644
index ff5165d4e7cf..000000000000
--- a/packages/SystemUI/tests/Android.mk
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (C) 2011 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_JACK_FLAGS := --multi-dex native
-LOCAL_DX_FLAGS := --multi-dex
-
-LOCAL_PACKAGE_NAME := SystemUITests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := device-tests
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- SystemUI-tests
-
-LOCAL_MULTILIB := both
-
-LOCAL_JNI_SHARED_LIBRARIES := \
- libdexmakerjvmtiagent \
- libmultiplejvmtiagentsinterferenceagent \
- libstaticjvmtiagent
-
-LOCAL_JAVA_LIBRARIES := \
- android.test.runner \
- telephony-common \
- android.test.base \
-
-LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui
-
-# sign this with platform cert, so this test is allowed to inject key events into
-# UI it doesn't own. This is necessary to allow screenshots to be taken
-LOCAL_CERTIFICATE := platform
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest.xml
-LOCAL_MANIFEST_FILE := AndroidManifest-base.xml
-
-# Provide jack a list of classes to exclude from code coverage.
-# This is needed because the SystemUITests compile SystemUI source directly, rather than using
-# LOCAL_INSTRUMENTATION_FOR := SystemUI.
-#
-# We want to exclude the test classes from code coverage measurements, but they share the same
-# package as the rest of SystemUI so they can't be easily filtered by package name.
-#
-# Generate a comma separated list of patterns based on the test source files under src/
-# SystemUI classes are in ../src/ so they won't be excluded.
-# Example:
-# Input files: src/com/android/systemui/Test.java src/com/android/systemui/AnotherTest.java
-# Generated exclude list: com.android.systemui.Test*,com.android.systemui.AnotherTest*
-
-# Filter all src files under src/ to just java files
-local_java_files := $(filter %.java,$(call all-java-files-under, src))
-# Transform java file names into full class names.
-# This only works if the class name matches the file name and the directory structure
-# matches the package.
-local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
-local_comma := ,
-local_empty :=
-local_space := $(local_empty) $(local_empty)
-# Convert class name list to jacoco exclude list
-# This appends a * to all classes and replace the space separators with commas.
-jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.*,com.android.keyguard.*
-LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
-
-ifeq ($(EXCLUDE_SYSTEMUI_TESTS),)
- include $(BUILD_PACKAGE)
-endif
-
-# Reset variables
-local_java_files :=
-local_classes :=
-local_comma :=
-local_space :=
-jacoco_exclude :=
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index ba2804572ef5..1b404a82145b 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -88,6 +88,11 @@
android:excludeFromRecents="true"
/>
+ <activity android:name=".settings.brightness.BrightnessDialogTest$TestDialog"
+ android:exported="false"
+ android:excludeFromRecents="true"
+ />
+
<activity android:name="com.android.systemui.screenshot.ScrollViewActivity"
android:exported="false" />
diff --git a/packages/SystemUI/tests/res/layout/custom_view_dark.xml b/packages/SystemUI/tests/res/layout/custom_view_dark.xml
index 9e460a5819a9..112d73d2d7f2 100644
--- a/packages/SystemUI/tests/res/layout/custom_view_dark.xml
+++ b/packages/SystemUI/tests/res/layout/custom_view_dark.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/custom_view_dark_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff000000"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
new file mode 100644
index 000000000000..7b9b39f23c29
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.util.AttributeSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class BouncerKeyguardMessageAreaTest : SysuiTestCase() {
+ class FakeBouncerKeyguardMessageArea(context: Context, attrs: AttributeSet?) :
+ BouncerKeyguardMessageArea(context, attrs) {
+ override val SHOW_DURATION_MILLIS = 0L
+ override val HIDE_DURATION_MILLIS = 0L
+ }
+ lateinit var underTest: BouncerKeyguardMessageArea
+
+ @Before
+ fun setup() {
+ underTest = FakeBouncerKeyguardMessageArea(context, null)
+ }
+
+ @Test
+ fun testSetSameMessage() {
+ val underTestSpy = spy(underTest)
+ underTestSpy.setMessage("abc")
+ underTestSpy.setMessage("abc")
+ verify(underTestSpy, times(1)).text = "abc"
+ }
+
+ @Test
+ fun testSetDifferentMessage() {
+ underTest.setMessage("abc")
+ underTest.setMessage("def")
+ assertThat(underTest.text).isEqualTo("def")
+ }
+
+ @Test
+ fun testSetNullMessage() {
+ underTest.setMessage(null)
+ assertThat(underTest.text).isEqualTo("")
+ }
+
+ @Test
+ fun testSetNullClearsPreviousMessage() {
+ underTest.setMessage("something not null")
+ assertThat(underTest.text).isEqualTo("something not null")
+
+ underTest.setMessage(null)
+ assertThat(underTest.text).isEqualTo("")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 8a2c35410586..1c3656d71d82 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -17,17 +17,22 @@ package com.android.keyguard
import android.content.BroadcastReceiver
import android.testing.AndroidTestingRunner
+import android.view.View
import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.mockito.any
@@ -37,6 +42,9 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import java.util.TimeZone
import java.util.concurrent.Executor
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
@@ -57,7 +65,7 @@ import org.mockito.junit.MockitoJUnit
class ClockEventControllerTest : SysuiTestCase() {
@JvmField @Rule val mockito = MockitoJUnit.rule()
- @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var keyguardInteractor: KeyguardInteractor
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var batteryController: BatteryController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@@ -72,8 +80,11 @@ class ClockEventControllerTest : SysuiTestCase() {
@Mock private lateinit var largeClockController: ClockFaceController
@Mock private lateinit var smallClockEvents: ClockFaceEvents
@Mock private lateinit var largeClockEvents: ClockFaceEvents
-
- private lateinit var clockEventController: ClockEventController
+ @Mock private lateinit var parentView: View
+ @Mock private lateinit var transitionRepository: KeyguardTransitionRepository
+ private lateinit var repository: FakeKeyguardRepository
+ @Mock private lateinit var logBuffer: LogBuffer
+ private lateinit var underTest: ClockEventController
@Before
fun setUp() {
@@ -86,8 +97,11 @@ class ClockEventControllerTest : SysuiTestCase() {
whenever(clock.events).thenReturn(events)
whenever(clock.animations).thenReturn(animations)
- clockEventController = ClockEventController(
- statusBarStateController,
+ repository = FakeKeyguardRepository()
+
+ underTest = ClockEventController(
+ KeyguardInteractor(repository = repository),
+ KeyguardTransitionInteractor(repository = transitionRepository),
broadcastDispatcher,
batteryController,
keyguardUpdateMonitor,
@@ -96,33 +110,36 @@ class ClockEventControllerTest : SysuiTestCase() {
context,
mainExecutor,
bgExecutor,
+ logBuffer,
featureFlags
)
+ underTest.clock = clock
+
+ runBlocking(IMMEDIATE) {
+ underTest.registerListeners(parentView)
+
+ repository.setDozing(true)
+ repository.setDozeAmount(1f)
+ }
}
@Test
fun clockSet_validateInitialization() {
- clockEventController.clock = clock
-
verify(clock).initialize(any(), anyFloat(), anyFloat())
}
@Test
fun clockUnset_validateState() {
- clockEventController.clock = clock
- clockEventController.clock = null
+ underTest.clock = null
- assertEquals(clockEventController.clock, null)
+ assertEquals(underTest.clock, null)
}
@Test
- fun themeChanged_verifyClockPaletteUpdated() {
- clockEventController.clock = clock
+ fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) {
verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
- clockEventController.registerListeners()
-
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
verify(configurationController).addCallback(capture(captor))
captor.value.onThemeChanged()
@@ -131,13 +148,10 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun fontChanged_verifyFontSizeUpdated() {
- clockEventController.clock = clock
+ fun fontChanged_verifyFontSizeUpdated() = runBlocking(IMMEDIATE) {
verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
- clockEventController.registerListeners()
-
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
verify(configurationController).addCallback(capture(captor))
captor.value.onDensityOrFontScaleChanged()
@@ -146,10 +160,7 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
verify(batteryController).addCallback(capture(batteryCaptor))
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
@@ -161,26 +172,21 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
- verify(batteryController).addCallback(capture(batteryCaptor))
- val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
- keyguardCaptor.value.onKeyguardVisibilityChanged(true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, true)
-
- verify(animations, times(1)).charge()
- }
+ fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() =
+ runBlocking(IMMEDIATE) {
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+
+ verify(animations, times(1)).charge()
+ }
@Test
- fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
verify(batteryController).addCallback(capture(batteryCaptor))
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
@@ -192,25 +198,20 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
- val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
- verify(batteryController).addCallback(capture(batteryCaptor))
- val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
- keyguardCaptor.value.onKeyguardVisibilityChanged(true)
- batteryCaptor.value.onBatteryLevelChanged(10, false, false)
-
- verify(animations, never()).charge()
- }
+ fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() =
+ runBlocking(IMMEDIATE) {
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, false)
+
+ verify(animations, never()).charge()
+ }
@Test
- fun localeCallback_verifyClockNotified() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun localeCallback_verifyClockNotified() = runBlocking(IMMEDIATE) {
val captor = argumentCaptor<BroadcastReceiver>()
verify(broadcastDispatcher).registerReceiver(
capture(captor), any(), eq(null), eq(null), anyInt(), eq(null)
@@ -221,10 +222,7 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun keyguardCallback_visibilityChanged_clockDozeCalled() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun keyguardCallback_visibilityChanged_clockDozeCalled() = runBlocking(IMMEDIATE) {
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
@@ -236,10 +234,7 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun keyguardCallback_timeFormat_clockNotified() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun keyguardCallback_timeFormat_clockNotified() = runBlocking(IMMEDIATE) {
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
captor.value.onTimeFormatChanged("12h")
@@ -248,11 +243,8 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun keyguardCallback_timezoneChanged_clockNotified() {
+ fun keyguardCallback_timezoneChanged_clockNotified() = runBlocking(IMMEDIATE) {
val mockTimeZone = mock<TimeZone>()
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
captor.value.onTimeZoneChanged(mockTimeZone)
@@ -261,10 +253,7 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun keyguardCallback_userSwitched_clockNotified() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
-
+ fun keyguardCallback_userSwitched_clockNotified() = runBlocking(IMMEDIATE) {
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
captor.value.onUserSwitchComplete(10)
@@ -273,25 +262,27 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun keyguardCallback_verifyKeyguardChanged() {
- clockEventController.clock = clock
- clockEventController.registerListeners()
+ fun keyguardCallback_verifyKeyguardChanged() = runBlocking(IMMEDIATE) {
+ val job = underTest.listenForDozeAmount(this)
+ repository.setDozeAmount(0.4f)
- val captor = argumentCaptor<StatusBarStateController.StateListener>()
- verify(statusBarStateController).addCallback(capture(captor))
- captor.value.onDozeAmountChanged(0.4f, 0.6f)
+ yield()
verify(animations).doze(0.4f)
+
+ job.cancel()
}
@Test
- fun unregisterListeners_validate() {
- clockEventController.clock = clock
- clockEventController.unregisterListeners()
+ fun unregisterListeners_validate() = runBlocking(IMMEDIATE) {
+ underTest.unregisterListeners()
verify(broadcastDispatcher).unregisterReceiver(any())
verify(configurationController).removeCallback(any())
verify(batteryController).removeCallback(any())
verify(keyguardUpdateMonitor).removeCallback(any())
- verify(statusBarStateController).removeCallback(any())
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 400caa3a352a..627d738a895f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.UserHandle;
import android.provider.Settings;
@@ -45,6 +46,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -262,9 +264,22 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
verify(mView).switchToClock(KeyguardClockSwitch.SMALL, /* animate */ true);
}
+ @Test
+ public void testGetClockAnimationsForwardsToClock() {
+ ClockController mockClockController = mock(ClockController.class);
+ ClockAnimations mockClockAnimations = mock(ClockAnimations.class);
+ when(mClockEventController.getClock()).thenReturn(mockClockController);
+ when(mockClockController.getAnimations()).thenReturn(mockClockAnimations);
+
+ Rect r1 = new Rect(1, 2, 3, 4);
+ Rect r2 = new Rect(5, 6, 7, 8);
+ mController.getClockAnimations().onPositionUpdated(r1, r2, 0.2f);
+ verify(mockClockAnimations).onPositionUpdated(r1, r2, 0.2f);
+ }
+
private void verifyAttachment(VerificationMode times) {
verify(mClockRegistry, times).registerClockChangeListener(
any(ClockRegistry.ClockChangeListener.class));
- verify(mClockEventController, times).registerListeners();
+ verify(mClockEventController, times).registerListeners(mView);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 69524e5a4537..5d2b0ca4e7ea 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -17,13 +17,11 @@
package com.android.keyguard;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -92,19 +90,4 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase {
mMessageAreaController.setIsVisible(true);
verify(mKeyguardMessageArea).setIsVisible(true);
}
-
- @Test
- public void testSetMessageIfEmpty_empty() {
- mMessageAreaController.setMessage("");
- mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
- verify(mKeyguardMessageArea).setMessage(R.string.keyguard_enter_your_pin);
- }
-
- @Test
- public void testSetMessageIfEmpty_notEmpty() {
- mMessageAreaController.setMessage("abc");
- mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
- verify(mKeyguardMessageArea, never()).setMessage(getContext()
- .getResources().getText(R.string.keyguard_enter_your_pin));
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index b89dbd98968a..b369098cafc0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -114,9 +114,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
}
@Test
- fun onResume_testSetInitialText() {
- keyguardPasswordViewController.onResume(KeyguardSecurityView.SCREEN_ON)
- verify(mKeyguardMessageAreaController)
- .setMessageIfEmpty(R.string.keyguard_enter_your_password)
+ fun startAppearAnimation() {
+ keyguardPasswordViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 3262a77b7711..9eff70487c74 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -100,16 +100,16 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
}
@Test
- fun onPause_clearsTextField() {
+ fun onPause_resetsText() {
mKeyguardPatternViewController.init()
mKeyguardPatternViewController.onPause()
- verify(mKeyguardMessageAreaController).setMessage("")
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
}
+
@Test
- fun onResume_setInitialText() {
- mKeyguardPatternViewController.onResume(KeyguardSecurityView.SCREEN_ON)
- verify(mKeyguardMessageAreaController)
- .setMessageIfEmpty(R.string.keyguard_enter_your_pattern)
+ fun startAppearAnimation() {
+ mKeyguardPatternViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 97d556b04aa4..ce1101f389c0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -113,11 +113,4 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
verify(mPasswordEntry).requestFocus();
}
-
- @Test
- public void onResume_setInitialText() {
- mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
- verify(mKeyguardMessageAreaController).setMessageIfEmpty(R.string.keyguard_enter_your_pin);
- }
}
-
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 9e5bfe53ea05..d9efdeaea04c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -98,6 +98,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Test
fun startAppearAnimation() {
pinViewController.startAppearAnimation()
- verify(keyguardMessageAreaController).setMessageIfEmpty(R.string.keyguard_enter_your_pin)
+ verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 48e82397e826..b885d546c517 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -146,6 +146,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
+ @Captor
+ private ArgumentCaptor<KeyguardSecurityContainer.SwipeListener> mSwipeListenerArgumentCaptor;
private Configuration mConfiguration;
@@ -475,6 +477,64 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
verify(mKeyguardUpdateMonitor, never()).getUserHasTrust(anyInt());
}
+ @Test
+ public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
+ KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
+ getRegisteredSwipeListener();
+ when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(false);
+ setupGetSecurityView();
+
+ registeredSwipeListener.onSwipeUp();
+
+ verify(mKeyguardUpdateMonitor).requestFaceAuth(true,
+ FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+ }
+
+ @Test
+ public void onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
+ KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
+ getRegisteredSwipeListener();
+ when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(true);
+
+ registeredSwipeListener.onSwipeUp();
+
+ verify(mKeyguardUpdateMonitor, never())
+ .requestFaceAuth(true,
+ FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+ }
+
+ @Test
+ public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
+ KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
+ getRegisteredSwipeListener();
+ when(mKeyguardUpdateMonitor.requestFaceAuth(true,
+ FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(true);
+ setupGetSecurityView();
+
+ registeredSwipeListener.onSwipeUp();
+
+ verify(mKeyguardPasswordViewControllerMock).showMessage(null, null);
+ }
+
+ @Test
+ public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
+ KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
+ getRegisteredSwipeListener();
+ when(mKeyguardUpdateMonitor.requestFaceAuth(true,
+ FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(false);
+ setupGetSecurityView();
+
+ registeredSwipeListener.onSwipeUp();
+
+ verify(mKeyguardPasswordViewControllerMock, never()).showMessage(null, null);
+ }
+
+ private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
+ mKeyguardSecurityContainerController.onViewAttached();
+ verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
+ return mSwipeListenerArgumentCaptor.getValue();
+ }
+
private void setupConditionsToEnableSideFpsHint() {
attachView();
setSideFpsHintEnabledFromResources(true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 4dcaa7cf8c09..c94c97c9b638 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,12 +16,16 @@
package com.android.keyguard;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.graphics.Rect;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -108,4 +112,16 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase {
configurationListenerArgumentCaptor.getValue().onLocaleListChanged();
verify(mKeyguardClockSwitchController).onLocaleListChanged();
}
+
+ @Test
+ public void getClockAnimations_forwardsToClockSwitch() {
+ ClockAnimations mockClockAnimations = mock(ClockAnimations.class);
+ when(mKeyguardClockSwitchController.getClockAnimations()).thenReturn(mockClockAnimations);
+
+ Rect r1 = new Rect(1, 2, 3, 4);
+ Rect r2 = new Rect(5, 6, 7, 8);
+ mController.getClockAnimations().onPositionUpdated(r1, r2, 0.3f);
+
+ verify(mockClockAnimations).onPositionUpdated(r1, r2, 0.3f);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 784e7ddb6d06..ebfb4d4d1778 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -25,6 +25,7 @@ import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
+import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.google.common.truth.Truth.assertThat;
@@ -647,6 +648,36 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
}
+ @Test
+ public void requestFaceAuth_whenFaceAuthWasStarted_returnsTrue() throws RemoteException {
+ // This satisfies all the preconditions to run face auth.
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ mTestableLooper.processAllMessages();
+
+ boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+ NOTIFICATION_PANEL_CLICKED);
+
+ assertThat(didFaceAuthRun).isTrue();
+ }
+
+ @Test
+ public void requestFaceAuth_whenFaceAuthWasNotStarted_returnsFalse() throws RemoteException {
+ // This ensures face auth won't run.
+ biometricsDisabledForCurrentUser();
+ mTestableLooper.processAllMessages();
+
+ boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+ NOTIFICATION_PANEL_CLICKED);
+
+ assertThat(didFaceAuthRun).isFalse();
+ }
+
private void testStrongAuthExceptOnBouncer(int strongAuth) {
when(mKeyguardBypassController.canBypass()).thenReturn(true);
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 2319f4386798..181839ab512f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -36,6 +36,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -255,6 +256,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
});
mScreenDecorations.mDisplayInfo = mDisplayInfo;
doReturn(1f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
+ doNothing().when(mScreenDecorations).updateOverlayProviderViews(any());
reset(mTunerService);
try {
@@ -1005,18 +1007,13 @@ public class ScreenDecorationsTest extends SysuiTestCase {
assertEquals(new Size(3, 3), resDelegate.getTopRoundedSize());
assertEquals(new Size(4, 4), resDelegate.getBottomRoundedSize());
- setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
- getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px)
- /* roundedTopDrawable */,
- getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px)
- /* roundedBottomDrawable */,
- 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/);
+ doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
mDisplayInfo.rotation = Surface.ROTATION_270;
mScreenDecorations.onConfigurationChanged(null);
- assertEquals(new Size(4, 4), resDelegate.getTopRoundedSize());
- assertEquals(new Size(5, 5), resDelegate.getBottomRoundedSize());
+ assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
+ assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
}
@Test
@@ -1293,51 +1290,6 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@Test
- public void testOnDisplayChanged_hwcLayer() {
- setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
- null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
- 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
- final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
- decorationSupport.format = PixelFormat.R_8;
- doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
-
- // top cutout
- mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
-
- mScreenDecorations.start();
-
- final ScreenDecorHwcLayer hwcLayer = mScreenDecorations.mScreenDecorHwcLayer;
- spyOn(hwcLayer);
- doReturn(mDisplay).when(hwcLayer).getDisplay();
-
- mScreenDecorations.mDisplayListener.onDisplayChanged(1);
-
- verify(hwcLayer, times(1)).onDisplayChanged(any());
- }
-
- @Test
- public void testOnDisplayChanged_nonHwcLayer() {
- setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
- null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
- 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
-
- // top cutout
- mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
-
- mScreenDecorations.start();
-
- final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView)
- mScreenDecorations.getOverlayView(R.id.display_cutout);
- assertNotNull(cutoutView);
- spyOn(cutoutView);
- doReturn(mDisplay).when(cutoutView).getDisplay();
-
- mScreenDecorations.mDisplayListener.onDisplayChanged(1);
-
- verify(cutoutView, times(1)).onDisplayChanged(any());
- }
-
- @Test
public void testHasSameProvidersWithNullOverlays() {
setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
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 986e7cdbf6ad..6ab54a374d30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -935,6 +935,251 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100)
}
+ /* ******** start of animatesViewRemoval_includeMarginsTrue tests ******** */
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_center() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalLeft = removedChild.left
+ val originalTop = removedChild.top
+ val originalRight = removedChild.right
+ val originalBottom = removedChild.bottom
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ val expectedX = ((originalLeft - M_LEFT) + (originalRight + M_RIGHT)) / 2
+ val expectedY = ((originalTop - M_TOP) + (originalBottom + M_BOTTOM)) / 2
+
+ checkBounds(
+ removedChild,
+ l = expectedX,
+ t = expectedY,
+ r = expectedX,
+ b = expectedY
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_left() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalLeft = removedChild.left
+ val originalTop = removedChild.top
+ val originalBottom = removedChild.bottom
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.LEFT,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalLeft - M_LEFT,
+ t = originalTop,
+ r = originalLeft - M_LEFT,
+ b = originalBottom
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_topLeft() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalLeft = removedChild.left
+ val originalTop = removedChild.top
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalLeft - M_LEFT,
+ t = originalTop - M_TOP,
+ r = originalLeft - M_LEFT,
+ b = originalTop - M_TOP
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_top() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalLeft = removedChild.left
+ val originalTop = removedChild.top
+ val originalRight = removedChild.right
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.TOP,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalLeft,
+ t = originalTop - M_TOP,
+ r = originalRight,
+ b = originalTop - M_TOP
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_topRight() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalTop = removedChild.top
+ val originalRight = removedChild.right
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalRight + M_RIGHT,
+ t = originalTop - M_TOP,
+ r = originalRight + M_RIGHT,
+ b = originalTop - M_TOP
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_right() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalTop = removedChild.top
+ val originalRight = removedChild.right
+ val originalBottom = removedChild.bottom
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.RIGHT,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalRight + M_RIGHT,
+ t = originalTop,
+ r = originalRight + M_RIGHT,
+ b = originalBottom
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_bottomRight() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalRight = removedChild.right
+ val originalBottom = removedChild.bottom
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalRight + M_RIGHT,
+ t = originalBottom + M_BOTTOM,
+ r = originalRight + M_RIGHT,
+ b = originalBottom + M_BOTTOM
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_bottom() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalLeft = removedChild.left
+ val originalRight = removedChild.right
+ val originalBottom = removedChild.bottom
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.BOTTOM,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalLeft,
+ t = originalBottom + M_BOTTOM,
+ r = originalRight,
+ b = originalBottom + M_BOTTOM
+ )
+ }
+
+ @Test
+ fun animatesViewRemoval_includeMarginsTrue_bottomLeft() {
+ setUpRootWithChildren(includeMarginsOnFirstChild = true)
+ val removedChild = rootView.getChildAt(0)
+ val originalLeft = removedChild.left
+ val originalBottom = removedChild.bottom
+
+ val success = ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
+ includeMargins = true,
+ )
+ forceLayout()
+
+ assertTrue(success)
+ assertNotNull(removedChild.getTag(R.id.tag_animator))
+ advanceAnimation(removedChild, 1.0f)
+ checkBounds(
+ removedChild,
+ l = originalLeft - M_LEFT,
+ t = originalBottom + M_BOTTOM,
+ r = originalLeft - M_LEFT,
+ b = originalBottom + M_BOTTOM
+ )
+ }
+ /* ******** end of animatesViewRemoval_includeMarginsTrue tests ******** */
+
@Test
fun animatesChildrenDuringViewRemoval() {
setUpRootWithChildren()
@@ -1215,7 +1460,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
}
- private fun setUpRootWithChildren() {
+ private fun setUpRootWithChildren(includeMarginsOnFirstChild: Boolean = false) {
rootView = LinearLayout(mContext)
(rootView as LinearLayout).orientation = LinearLayout.HORIZONTAL
(rootView as LinearLayout).weightSum = 1f
@@ -1229,13 +1474,26 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val secondChild = View(mContext)
rootView.addView(secondChild)
- val childParams = LinearLayout.LayoutParams(
+ val firstChildParams = LinearLayout.LayoutParams(
+ 0 /* width */,
+ LinearLayout.LayoutParams.MATCH_PARENT
+ )
+ firstChildParams.weight = 0.5f
+ if (includeMarginsOnFirstChild) {
+ firstChildParams.leftMargin = M_LEFT
+ firstChildParams.topMargin = M_TOP
+ firstChildParams.rightMargin = M_RIGHT
+ firstChildParams.bottomMargin = M_BOTTOM
+ }
+ firstChild.layoutParams = firstChildParams
+
+ val secondChildParams = LinearLayout.LayoutParams(
0 /* width */,
LinearLayout.LayoutParams.MATCH_PARENT
)
- childParams.weight = 0.5f
- firstChild.layoutParams = childParams
- secondChild.layoutParams = childParams
+ secondChildParams.weight = 0.5f
+ secondChild.layoutParams = secondChildParams
+
firstGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */)
(firstGrandChild.layoutParams as RelativeLayout.LayoutParams)
.addRule(RelativeLayout.ALIGN_PARENT_START)
@@ -1315,3 +1573,9 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
}
}
}
+
+// Margin values.
+private const val M_LEFT = 14
+private const val M_TOP = 16
+private const val M_RIGHT = 18
+private const val M_BOTTOM = 20
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index baeabc577fb7..c85334db9499 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_
import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
@@ -124,14 +125,18 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
whenever(udfpsEnrollView.context).thenReturn(context)
}
- private fun withReason(@ShowReason reason: Int, block: () -> Unit) {
+ private fun withReason(
+ @ShowReason reason: Int,
+ isDebuggable: Boolean = false,
+ block: () -> Unit
+ ) {
controllerOverlay = UdfpsControllerOverlay(
context, fingerprintManager, inflater, windowManager, accessibilityManager,
statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager,
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
configurationController, systemClock, keyguardStateController,
unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
- controllerCallback, onTouch, activityLaunchAnimator
+ controllerCallback, onTouch, activityLaunchAnimator, isDebuggable
)
block()
}
@@ -151,11 +156,29 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
}
@Test
+ fun showUdfpsOverlay_locate_withEnrollmentUiRemoved() {
+ Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 1)
+ withReason(REASON_ENROLL_FIND_SENSOR, isDebuggable = true) {
+ showUdfpsOverlay(isEnrollUseCase = false)
+ }
+ Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 0)
+ }
+
+ @Test
fun showUdfpsOverlay_enroll() = withReason(REASON_ENROLL_ENROLLING) {
showUdfpsOverlay(isEnrollUseCase = true)
}
@Test
+ fun showUdfpsOverlay_enroll_withEnrollmentUiRemoved() {
+ Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 1)
+ withReason(REASON_ENROLL_ENROLLING, isDebuggable = true) {
+ showUdfpsOverlay(isEnrollUseCase = false)
+ }
+ Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 0)
+ }
+
+ @Test
fun showUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { showUdfpsOverlay() }
private fun withRotation(@Rotation rotation: Int, block: () -> Unit) {
@@ -372,21 +395,33 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
val rotation = Surface.ROTATION_0
// touch at 0 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[0])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, 0.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[0])
// touch at 90 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[1])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, -1.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[1])
// touch at 180 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[2])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ -1.0f /* x */, 0.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[2])
// touch at 270 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[3])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, 1.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[3])
}
fun testTouchOutsideAreaNoRotation90Degrees() = withReason(REASON_ENROLL_ENROLLING) {
@@ -394,21 +429,33 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
val rotation = Surface.ROTATION_90
// touch at 0 degrees -> 90 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[1])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, 0.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[1])
// touch at 90 degrees -> 180 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[2])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, -1.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[2])
// touch at 180 degrees -> 270 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[3])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ -1.0f /* x */, 0.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[3])
// touch at 270 degrees -> 0 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[0])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, 1.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[0])
}
fun testTouchOutsideAreaNoRotation270Degrees() = withReason(REASON_ENROLL_ENROLLING) {
@@ -416,21 +463,33 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
val rotation = Surface.ROTATION_270
// touch at 0 degrees -> 270 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[3])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, 0.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[3])
// touch at 90 degrees -> 0 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[0])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, -1.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[0])
// touch at 180 degrees -> 90 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[1])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ -1.0f /* x */, 0.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[1])
// touch at 270 degrees -> 180 degrees
- assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */,
- 0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
- .isEqualTo(touchHints[2])
+ assertThat(
+ controllerOverlay.onTouchOutsideOfSensorAreaImpl(
+ 0.0f /* x */, 1.0f /* y */,
+ 0.0f /* sensorX */, 0.0f /* sensorY */, rotation
+ )
+ ).isEqualTo(touchHints[2])
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 8923ba817568..28e13b8e81ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -169,6 +169,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Mock
private LatencyTracker mLatencyTracker;
private FakeExecutor mFgExecutor;
+ @Mock
+ private UdfpsDisplayMode mUdfpsDisplayMode;
// Stuff for configuring mocks
@Mock
@@ -258,7 +260,6 @@ public class UdfpsControllerTest extends SysuiTestCase {
mVibrator,
mUdfpsHapticsSimulator,
mUdfpsShell,
- Optional.of(mDisplayModeProvider),
mKeyguardStateController,
mDisplayManager,
mHandler,
@@ -275,6 +276,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
mScreenObserver = mScreenObserverCaptor.getValue();
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, new UdfpsOverlayParams());
+ mUdfpsController.setUdfpsDisplayMode(mUdfpsDisplayMode);
}
@Test
@@ -659,7 +661,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
// WHEN it is cancelled
- mUdfpsController.onCancelUdfps();
+ mUdfpsController.cancelAodInterrupt();
// THEN the display is unconfigured
verify(mUdfpsView).unconfigureDisplay();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
new file mode 100644
index 000000000000..7e35b261b352
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecution;
+
+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)
+@RunWithLooper(setAsMainLooper = true)
+public class UdfpsDisplayModeTest extends SysuiTestCase {
+ private static final int DISPLAY_ID = 0;
+
+ @Mock
+ private AuthController mAuthController;
+ @Mock
+ private IUdfpsHbmListener mDisplayCallback;
+ @Mock
+ private Runnable mOnEnabled;
+ @Mock
+ private Runnable mOnDisabled;
+
+ private final FakeExecution mExecution = new FakeExecution();
+ private UdfpsDisplayMode mHbmController;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ // Force mContext to always return DISPLAY_ID
+ Context contextSpy = spy(mContext);
+ when(contextSpy.getDisplayId()).thenReturn(DISPLAY_ID);
+
+ // Set up mocks.
+ when(mAuthController.getUdfpsHbmListener()).thenReturn(mDisplayCallback);
+
+ // Create a real controller with mock dependencies.
+ mHbmController = new UdfpsDisplayMode(contextSpy, mExecution, mAuthController);
+ }
+
+ @Test
+ public void roundTrip() throws RemoteException {
+ // Enable the UDFPS mode.
+ mHbmController.enable(mOnEnabled);
+
+ // Should set the appropriate refresh rate for UDFPS and notify the caller.
+ verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+ verify(mOnEnabled).run();
+
+ // Disable the UDFPS mode.
+ mHbmController.disable(mOnDisabled);
+
+ // Should unset the refresh rate and notify the caller.
+ verify(mOnDisabled).run();
+ verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID));
+ }
+
+ @Test
+ public void mustNotEnableMoreThanOnce() throws RemoteException {
+ // First request to enable the UDFPS mode.
+ mHbmController.enable(mOnEnabled);
+
+ // Should set the appropriate refresh rate for UDFPS and notify the caller.
+ verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+ verify(mOnEnabled).run();
+
+ // Second request to enable the UDFPS mode, while it's still enabled.
+ mHbmController.enable(mOnEnabled);
+
+ // Should ignore the second request.
+ verifyNoMoreInteractions(mDisplayCallback);
+ verifyNoMoreInteractions(mOnEnabled);
+ }
+
+ @Test
+ public void mustNotDisableMoreThanOnce() throws RemoteException {
+ // Disable the UDFPS mode.
+ mHbmController.enable(mOnEnabled);
+
+ // Should set the appropriate refresh rate for UDFPS and notify the caller.
+ verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+ verify(mOnEnabled).run();
+
+ // First request to disable the UDFPS mode.
+ mHbmController.disable(mOnDisabled);
+
+ // Should unset the refresh rate and notify the caller.
+ verify(mOnDisabled).run();
+ verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID));
+
+ // Second request to disable the UDFPS mode, when it's already disabled.
+ mHbmController.disable(mOnDisabled);
+
+ // Should ignore the second request.
+ verifyNoMoreInteractions(mOnDisabled);
+ verifyNoMoreInteractions(mDisplayCallback);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index b2a9e8209495..6bc7308a6a40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -145,6 +145,35 @@ public class BrightLineClassifierTest extends SysuiTestCase {
}
@Test
+ public void testIsFalseTouch_SeekBar_FalseTouch() {
+ when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mFalsedResult);
+ when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).isTrue();
+ }
+
+ @Test
+ public void testIsFalseTouch_SeekBar_RealTouch() {
+ when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).isFalse();
+ }
+
+ @Test
+ public void testIsFalseTouch_SeekBar_FalseTap() {
+ when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mFalsedResult);
+ when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).isTrue();
+ }
+
+ @Test
+ public void testIsFalseTouch_SeekBar_RealTap() {
+ when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
+ .thenReturn(mFalsedResult);
+ assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).isFalse();
+ }
+
+ @Test
public void testIsFalseTouch_ClassifierBRejects() {
when(mClassifierB.classifyGesture(anyInt(), anyDouble(), anyDouble()))
.thenReturn(mFalsedResult);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 3e9cf1e51b63..fa9c41a3cbb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -35,6 +35,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -71,6 +72,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
@Mock
private KeyguardStateController mKeyguardStateController;
@Mock
+ private ShadeExpansionStateManager mShadeExpansionStateManager;
+ @Mock
private BatteryController mBatteryController;
private final DockManagerFake mDockManager = new DockManagerFake();
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -85,7 +88,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager,
mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
- mStatusBarStateController, mKeyguardStateController, mBatteryController,
+ mStatusBarStateController, mKeyguardStateController, mShadeExpansionStateManager,
+ mBatteryController,
mDockManager, mFakeExecutor, mFakeSystemClock);
}
@@ -137,9 +141,9 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
public void testUnregisterSensor_QS() {
mFalsingCollector.onScreenTurningOn();
reset(mProximitySensor);
- mFalsingCollector.setQsExpanded(true);
+ mFalsingCollector.onQsExpansionChanged(true);
verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class));
- mFalsingCollector.setQsExpanded(false);
+ mFalsingCollector.onQsExpansionChanged(false);
verify(mProximitySensor).register(any(ThresholdSensor.Listener.class));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index 91214a85ddd5..e7e6918325a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -38,6 +38,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.util.DeviceConfigProxyFake;
import org.junit.Before;
@@ -47,6 +49,9 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import javax.inject.Provider;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -55,11 +60,15 @@ public class ClipboardListenerTest extends SysuiTestCase {
@Mock
private ClipboardManager mClipboardManager;
@Mock
- private ClipboardOverlayControllerFactory mClipboardOverlayControllerFactory;
+ private ClipboardOverlayControllerLegacyFactory mClipboardOverlayControllerLegacyFactory;
+ @Mock
+ private ClipboardOverlayControllerLegacy mOverlayControllerLegacy;
@Mock
private ClipboardOverlayController mOverlayController;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private FeatureFlags mFeatureFlags;
private DeviceConfigProxyFake mDeviceConfigProxy;
private ClipData mSampleClipData;
@@ -72,12 +81,17 @@ public class ClipboardListenerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<String> mStringCaptor;
+ @Spy
+ private Provider<ClipboardOverlayController> mOverlayControllerProvider;
+
@Before
public void setup() {
+ mOverlayControllerProvider = () -> mOverlayController;
+
MockitoAnnotations.initMocks(this);
- when(mClipboardOverlayControllerFactory.create(any())).thenReturn(
- mOverlayController);
+ when(mClipboardOverlayControllerLegacyFactory.create(any()))
+ .thenReturn(mOverlayControllerLegacy);
when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
@@ -94,7 +108,8 @@ public class ClipboardListenerTest extends SysuiTestCase {
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"false", false);
ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardManager, mUiEventLogger, mFeatureFlags);
listener.start();
verifyZeroInteractions(mClipboardManager);
verifyZeroInteractions(mUiEventLogger);
@@ -105,7 +120,8 @@ public class ClipboardListenerTest extends SysuiTestCase {
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"true", false);
ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardManager, mUiEventLogger, mFeatureFlags);
listener.start();
verify(mClipboardManager).addPrimaryClipChangedListener(any());
verifyZeroInteractions(mUiEventLogger);
@@ -113,16 +129,58 @@ public class ClipboardListenerTest extends SysuiTestCase {
@Test
public void test_consecutiveCopies() {
+ when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
+
mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
"true", false);
ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
- mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardManager, mUiEventLogger, mFeatureFlags);
listener.start();
listener.onPrimaryClipChanged();
- verify(mClipboardOverlayControllerFactory).create(any());
+ verify(mClipboardOverlayControllerLegacyFactory).create(any());
- verify(mOverlayController).setClipData(mClipDataCaptor.capture(), mStringCaptor.capture());
+ verify(mOverlayControllerLegacy).setClipData(
+ mClipDataCaptor.capture(), mStringCaptor.capture());
+
+ assertEquals(mSampleClipData, mClipDataCaptor.getValue());
+ assertEquals(mSampleSource, mStringCaptor.getValue());
+
+ verify(mOverlayControllerLegacy).setOnSessionCompleteListener(mRunnableCaptor.capture());
+
+ // Should clear the overlay controller
+ mRunnableCaptor.getValue().run();
+
+ listener.onPrimaryClipChanged();
+
+ verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
+
+ // Not calling the runnable here, just change the clip again and verify that the overlay is
+ // NOT recreated.
+
+ listener.onPrimaryClipChanged();
+
+ verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
+ verifyZeroInteractions(mOverlayControllerProvider);
+ }
+
+ @Test
+ public void test_consecutiveCopies_new() {
+ when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
+
+ mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
+ "true", false);
+ ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardManager, mUiEventLogger, mFeatureFlags);
+ listener.start();
+ listener.onPrimaryClipChanged();
+
+ verify(mOverlayControllerProvider).get();
+
+ verify(mOverlayController).setClipData(
+ mClipDataCaptor.capture(), mStringCaptor.capture());
assertEquals(mSampleClipData, mClipDataCaptor.getValue());
assertEquals(mSampleSource, mStringCaptor.getValue());
@@ -134,14 +192,15 @@ public class ClipboardListenerTest extends SysuiTestCase {
listener.onPrimaryClipChanged();
- verify(mClipboardOverlayControllerFactory, times(2)).create(any());
+ verify(mOverlayControllerProvider, times(2)).get();
// Not calling the runnable here, just change the clip again and verify that the overlay is
// NOT recreated.
listener.onPrimaryClipChanged();
- verify(mClipboardOverlayControllerFactory, times(2)).create(any());
+ verify(mOverlayControllerProvider, times(2)).get();
+ verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
}
@Test
@@ -169,4 +228,40 @@ public class ClipboardListenerTest extends SysuiTestCase {
assertTrue(ClipboardListener.shouldSuppressOverlay(suppressableClipData,
ClipboardListener.SHELL_PACKAGE, false));
}
+
+ @Test
+ public void test_logging_enterAndReenter() {
+ when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
+
+ ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardManager, mUiEventLogger, mFeatureFlags);
+ listener.start();
+
+ listener.onPrimaryClipChanged();
+ listener.onPrimaryClipChanged();
+
+ verify(mUiEventLogger, times(1)).log(
+ ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+ verify(mUiEventLogger, times(1)).log(
+ ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
+ }
+
+ @Test
+ public void test_logging_enterAndReenter_new() {
+ when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
+
+ ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+ mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+ mClipboardManager, mUiEventLogger, mFeatureFlags);
+ listener.start();
+
+ listener.onPrimaryClipChanged();
+ listener.onPrimaryClipChanged();
+
+ verify(mUiEventLogger, times(1)).log(
+ ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+ verify(mUiEventLogger, times(1)).log(
+ ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
new file mode 100644
index 000000000000..b7f1c1a9f001
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay;
+
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.screenshot.TimeoutHandler;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ClipboardOverlayControllerTest extends SysuiTestCase {
+
+ private ClipboardOverlayController mOverlayController;
+ @Mock
+ private ClipboardOverlayView mClipboardOverlayView;
+ @Mock
+ private ClipboardOverlayWindow mClipboardOverlayWindow;
+ @Mock
+ private BroadcastSender mBroadcastSender;
+ @Mock
+ private TimeoutHandler mTimeoutHandler;
+ @Mock
+ private UiEventLogger mUiEventLogger;
+
+ @Mock
+ private Animator mAnimator;
+
+ private ClipData mSampleClipData;
+
+ @Captor
+ private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor;
+ private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks;
+
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
+ when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
+
+ mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
+ new ClipData.Item("Test Item"));
+
+ mOverlayController = new ClipboardOverlayController(
+ mContext,
+ mClipboardOverlayView,
+ mClipboardOverlayWindow,
+ getFakeBroadcastDispatcher(),
+ mBroadcastSender,
+ mTimeoutHandler,
+ mUiEventLogger);
+ verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
+ mCallbacks = mOverlayCallbacksCaptor.getValue();
+ }
+
+ @After
+ public void tearDown() {
+ mOverlayController.hideImmediate();
+ }
+
+ @Test
+ public void test_setClipData_nullData() {
+ ClipData clipData = null;
+ mOverlayController.setClipData(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(0)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_invalidImageData() {
+ ClipData clipData = new ClipData("", new String[]{"image/png"},
+ new ClipData.Item(Uri.parse("")));
+
+ mOverlayController.setClipData(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(0)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_textData() {
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_sensitiveTextData() {
+ ClipDescription description = mSampleClipData.getDescription();
+ PersistableBundle b = new PersistableBundle();
+ b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
+ description.setExtras(b);
+ ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
+ mOverlayController.setClipData(data, "");
+
+ verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_repeatedCalls() {
+ when(mAnimator.isRunning()).thenReturn(true);
+
+ mOverlayController.setClipData(mSampleClipData, "");
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_viewCallbacks_onShareTapped() {
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ mCallbacks.onShareButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED);
+ verify(mClipboardOverlayView, times(1)).getExitAnimation();
+ }
+
+ @Test
+ public void test_viewCallbacks_onDismissTapped() {
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ verify(mClipboardOverlayView, times(1)).getExitAnimation();
+ }
+
+ @Test
+ public void test_multipleDismissals_dismissesOnce() {
+ mCallbacks.onSwipeDismissInitiated(mAnimator);
+ mCallbacks.onDismissButtonTapped();
+ mCallbacks.onSwipeDismissInitiated(mAnimator);
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+ verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java
deleted file mode 100644
index c7c2cd8d7b4b..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.clipboardoverlay;
-
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.provider.DeviceConfig;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.DeviceConfigProxyFake;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ClipboardOverlayEventTest extends SysuiTestCase {
-
- @Mock
- private ClipboardManager mClipboardManager;
- @Mock
- private ClipboardOverlayControllerFactory mClipboardOverlayControllerFactory;
- @Mock
- private ClipboardOverlayController mOverlayController;
- @Mock
- private UiEventLogger mUiEventLogger;
-
- private final String mSampleSource = "Example source";
-
- private ClipboardListener mClipboardListener;
-
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- when(mClipboardOverlayControllerFactory.create(any())).thenReturn(
- mOverlayController);
- when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
-
- ClipData sampleClipData = new ClipData("Test", new String[]{"text/plain"},
- new ClipData.Item("Test Item"));
- when(mClipboardManager.getPrimaryClip()).thenReturn(sampleClipData);
- when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
-
- DeviceConfigProxyFake deviceConfigProxy = new DeviceConfigProxyFake();
- deviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
- "true", false);
-
- mClipboardListener = new ClipboardListener(getContext(), deviceConfigProxy,
- mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
- }
-
- @Test
- public void test_enterAndReenter() {
- mClipboardListener.start();
-
- mClipboardListener.onPrimaryClipChanged();
- mClipboardListener.onPrimaryClipChanged();
-
- verify(mUiEventLogger, times(1)).log(
- ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
- verify(mUiEventLogger, times(1)).log(
- ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
index f93336134900..93a1868b72f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
@@ -24,12 +24,11 @@ import androidx.annotation.DrawableRes
import androidx.test.filters.SmallTest
import com.android.internal.R as InternalR
import com.android.systemui.R as SystemUIR
-import com.android.systemui.tests.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.tests.R
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
-
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@@ -102,14 +101,11 @@ class RoundedCornerResDelegateTest : SysuiTestCase() {
assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
- setupResources(radius = 100,
- roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px),
- roundedBottomDrawable = getTestsDrawable(R.drawable.rounded5px))
-
+ roundedCornerResDelegate.physicalPixelDisplaySizeRatio = 2f
roundedCornerResDelegate.updateDisplayUniqueId(null, 1)
- assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize)
- assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize)
+ assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize)
+ assertEquals(Size(8, 8), roundedCornerResDelegate.bottomRoundedSize)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 6a55a60c2fda..5bbd8109d8f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -16,6 +16,9 @@
package com.android.systemui.doze;
+import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
+
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
@@ -38,16 +41,17 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.UiModeManager;
import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
import android.view.Display;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -78,25 +82,30 @@ public class DozeMachineTest extends SysuiTestCase {
@Mock
private DozeHost mHost;
@Mock
- private UiModeManager mUiModeManager;
+ private DozeMachine.Part mPartMock;
+ @Mock
+ private DozeMachine.Part mAnotherPartMock;
private DozeServiceFake mServiceFake;
private WakeLockFake mWakeLockFake;
- private AmbientDisplayConfiguration mConfigMock;
- private DozeMachine.Part mPartMock;
+ private AmbientDisplayConfiguration mAmbientDisplayConfigMock;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mServiceFake = new DozeServiceFake();
mWakeLockFake = new WakeLockFake();
- mConfigMock = mock(AmbientDisplayConfiguration.class);
- mPartMock = mock(DozeMachine.Part.class);
+ mAmbientDisplayConfigMock = mock(AmbientDisplayConfiguration.class);
when(mDockManager.isDocked()).thenReturn(false);
when(mDockManager.isHidden()).thenReturn(false);
- mMachine = new DozeMachine(mServiceFake, mConfigMock, mWakeLockFake,
- mWakefulnessLifecycle, mUiModeManager, mDozeLog, mDockManager,
- mHost, new DozeMachine.Part[]{mPartMock});
+ mMachine = new DozeMachine(mServiceFake,
+ mAmbientDisplayConfigMock,
+ mWakeLockFake,
+ mWakefulnessLifecycle,
+ mDozeLog,
+ mDockManager,
+ mHost,
+ new DozeMachine.Part[]{mPartMock, mAnotherPartMock});
}
@Test
@@ -108,7 +117,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_goesToDoze() {
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
mMachine.requestState(INITIALIZED);
@@ -118,7 +127,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_goesToAod() {
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
mMachine.requestState(INITIALIZED);
@@ -138,7 +147,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_afterDockPaused_goesToDoze() {
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
when(mDockManager.isDocked()).thenReturn(true);
when(mDockManager.isHidden()).thenReturn(true);
@@ -151,7 +160,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_alwaysOnSuppressed_alwaysOnDisabled_goesToDoze() {
when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
mMachine.requestState(INITIALIZED);
@@ -162,7 +171,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_alwaysOnSuppressed_alwaysOnEnabled_goesToDoze() {
when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
mMachine.requestState(INITIALIZED);
@@ -184,7 +193,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_alwaysOnSuppressed_alwaysOnDisabled_afterDockPaused_goesToDoze() {
when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
when(mDockManager.isDocked()).thenReturn(true);
when(mDockManager.isHidden()).thenReturn(true);
@@ -197,7 +206,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testInitialize_alwaysOnSuppressed_alwaysOnEnabled_afterDockPaused_goesToDoze() {
when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
when(mDockManager.isDocked()).thenReturn(true);
when(mDockManager.isHidden()).thenReturn(true);
@@ -209,7 +218,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testPulseDone_goesToDoze() {
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false);
mMachine.requestState(INITIALIZED);
mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
mMachine.requestState(DOZE_PULSING);
@@ -222,7 +231,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testPulseDone_goesToAoD() {
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
mMachine.requestState(INITIALIZED);
mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
mMachine.requestState(DOZE_PULSING);
@@ -236,7 +245,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testPulseDone_alwaysOnSuppressed_goesToSuppressed() {
when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
mMachine.requestState(INITIALIZED);
mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
mMachine.requestState(DOZE_PULSING);
@@ -287,7 +296,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testPulseDone_afterDockPaused_goesToDoze() {
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
when(mDockManager.isDocked()).thenReturn(true);
when(mDockManager.isHidden()).thenReturn(true);
mMachine.requestState(INITIALIZED);
@@ -303,7 +312,7 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testPulseDone_alwaysOnSuppressed_afterDockPaused_goesToDoze() {
when(mHost.isAlwaysOnSuppressed()).thenReturn(true);
- when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true);
when(mDockManager.isDocked()).thenReturn(true);
when(mDockManager.isHidden()).thenReturn(true);
mMachine.requestState(INITIALIZED);
@@ -471,7 +480,9 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testTransitionToInitialized_carModeIsEnabled() {
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ Configuration configuration = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(configuration);
mMachine.requestState(INITIALIZED);
verify(mPartMock).transitionTo(UNINITIALIZED, INITIALIZED);
@@ -481,7 +492,9 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testTransitionToFinish_carModeIsEnabled() {
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ Configuration configuration = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(configuration);
mMachine.requestState(INITIALIZED);
mMachine.requestState(FINISH);
@@ -490,7 +503,9 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testDozeToDozeSuspendTriggers_carModeIsEnabled() {
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ Configuration configuration = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(configuration);
mMachine.requestState(INITIALIZED);
mMachine.requestState(DOZE);
@@ -499,7 +514,9 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testDozeAoDToDozeSuspendTriggers_carModeIsEnabled() {
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ Configuration configuration = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(configuration);
mMachine.requestState(INITIALIZED);
mMachine.requestState(DOZE_AOD);
@@ -508,7 +525,9 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testDozePulsingBrightDozeSuspendTriggers_carModeIsEnabled() {
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ Configuration configuration = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(configuration);
mMachine.requestState(INITIALIZED);
mMachine.requestState(DOZE_PULSING_BRIGHT);
@@ -517,7 +536,9 @@ public class DozeMachineTest extends SysuiTestCase {
@Test
public void testDozeAodDockedDozeSuspendTriggers_carModeIsEnabled() {
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ Configuration configuration = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(configuration);
mMachine.requestState(INITIALIZED);
mMachine.requestState(DOZE_AOD_DOCKED);
@@ -525,7 +546,35 @@ public class DozeMachineTest extends SysuiTestCase {
}
@Test
+ public void testOnConfigurationChanged_propagatesUiModeTypeToParts() {
+ Configuration newConfig = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(newConfig);
+
+ verify(mPartMock).onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ verify(mAnotherPartMock).onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ }
+
+ @Test
+ public void testOnConfigurationChanged_propagatesOnlyUiModeChangesToParts() {
+ Configuration newConfig = configWithCarNightUiMode();
+
+ mMachine.onConfigurationChanged(newConfig);
+ mMachine.onConfigurationChanged(newConfig);
+
+ verify(mPartMock, times(1)).onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ verify(mAnotherPartMock, times(1)).onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ }
+
+ @Test
public void testDozeSuppressTriggers_screenState() {
assertEquals(Display.STATE_OFF, DOZE_SUSPEND_TRIGGERS.screenState(null));
}
+
+ @NonNull
+ private Configuration configWithCarNightUiMode() {
+ Configuration configuration = Configuration.EMPTY;
+ configuration.uiMode = UI_MODE_TYPE_CAR | UI_MODE_NIGHT_YES;
+ return configuration;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 9ffc5a57cef6..c40c187d572a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -423,7 +423,7 @@ public class DozeSensorsTest extends SysuiTestCase {
@Test
public void testGesturesAllInitiallyRespectSettings() {
- DozeSensors dozeSensors = new DozeSensors(getContext(), mSensorManager, mDozeParameters,
+ DozeSensors dozeSensors = new DozeSensors(mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
mDevicePostureController);
@@ -435,7 +435,7 @@ public class DozeSensorsTest extends SysuiTestCase {
private class TestableDozeSensors extends DozeSensors {
TestableDozeSensors() {
- super(getContext(), mSensorManager, mDozeParameters,
+ super(mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
mDevicePostureController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
index 0f29dcd5a939..32b994538e12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
@@ -10,14 +10,14 @@
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions andatest
+ * See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.doze;
-import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE;
-import static android.app.UiModeManager.ACTION_EXIT_CAR_MODE;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
+import static android.content.res.Configuration.UI_MODE_TYPE_NORMAL;
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
@@ -26,17 +26,16 @@ import static com.android.systemui.doze.DozeMachine.State.FINISH;
import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.UiModeManager;
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
@@ -44,13 +43,13 @@ import android.testing.UiThreadTest;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.AdditionalMatchers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
@@ -71,10 +70,6 @@ public class DozeSuppressorTest extends SysuiTestCase {
@Mock
private AmbientDisplayConfiguration mConfig;
@Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
- private UiModeManager mUiModeManager;
- @Mock
private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@Mock
private BiometricUnlockController mBiometricUnlockController;
@@ -83,13 +78,6 @@ public class DozeSuppressorTest extends SysuiTestCase {
private DozeMachine mDozeMachine;
@Captor
- private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
- @Captor
- private ArgumentCaptor<IntentFilter> mIntentFilterCaptor;
- private BroadcastReceiver mBroadcastReceiver;
- private IntentFilter mIntentFilter;
-
- @Captor
private ArgumentCaptor<DozeHost.Callback> mDozeHostCaptor;
private DozeHost.Callback mDozeHostCallback;
@@ -106,8 +94,6 @@ public class DozeSuppressorTest extends SysuiTestCase {
mDozeHost,
mConfig,
mDozeLog,
- mBroadcastDispatcher,
- mUiModeManager,
mBiometricUnlockControllerLazy);
mDozeSuppressor.setDozeMachine(mDozeMachine);
@@ -122,36 +108,35 @@ public class DozeSuppressorTest extends SysuiTestCase {
public void testRegistersListenersOnInitialized_unregisteredOnFinish() {
// check that receivers and callbacks registered
mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
- captureBroadcastReceiver();
captureDozeHostCallback();
// check that receivers and callbacks are unregistered
mDozeSuppressor.transitionTo(INITIALIZED, FINISH);
- verify(mBroadcastDispatcher).unregisterReceiver(mBroadcastReceiver);
verify(mDozeHost).removeCallback(mDozeHostCallback);
}
@Test
public void testSuspendTriggersDoze_carMode() {
// GIVEN car mode
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
// WHEN dozing begins
mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
// THEN doze continues with all doze triggers disabled.
- verify(mDozeMachine).requestState(DOZE_SUSPEND_TRIGGERS);
+ verify(mDozeMachine, atLeastOnce()).requestState(DOZE_SUSPEND_TRIGGERS);
+ verify(mDozeMachine, never())
+ .requestState(AdditionalMatchers.not(eq(DOZE_SUSPEND_TRIGGERS)));
}
@Test
public void testSuspendTriggersDoze_enterCarMode() {
// GIVEN currently dozing
mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
- captureBroadcastReceiver();
mDozeSuppressor.transitionTo(INITIALIZED, DOZE);
// WHEN car mode entered
- mBroadcastReceiver.onReceive(null, new Intent(ACTION_ENTER_CAR_MODE));
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
// THEN doze continues with all doze triggers disabled.
verify(mDozeMachine).requestState(DOZE_SUSPEND_TRIGGERS);
@@ -160,13 +145,13 @@ public class DozeSuppressorTest extends SysuiTestCase {
@Test
public void testDozeResume_exitCarMode() {
// GIVEN currently suspended, with AOD not enabled
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(false);
mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
- captureBroadcastReceiver();
mDozeSuppressor.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS);
// WHEN exiting car mode
- mBroadcastReceiver.onReceive(null, new Intent(ACTION_EXIT_CAR_MODE));
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_NORMAL);
// THEN doze is resumed
verify(mDozeMachine).requestState(DOZE);
@@ -175,19 +160,53 @@ public class DozeSuppressorTest extends SysuiTestCase {
@Test
public void testDozeAoDResume_exitCarMode() {
// GIVEN currently suspended, with AOD not enabled
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(true);
mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
- captureBroadcastReceiver();
mDozeSuppressor.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS);
// WHEN exiting car mode
- mBroadcastReceiver.onReceive(null, new Intent(ACTION_EXIT_CAR_MODE));
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_NORMAL);
// THEN doze AOD is resumed
verify(mDozeMachine).requestState(DOZE_AOD);
}
@Test
+ public void testUiModeDoesNotChange_noStateTransition() {
+ mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+ clearInvocations(mDozeMachine);
+
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+
+ verify(mDozeMachine, times(1)).requestState(DOZE_SUSPEND_TRIGGERS);
+ verify(mDozeMachine, never())
+ .requestState(AdditionalMatchers.not(eq(DOZE_SUSPEND_TRIGGERS)));
+ }
+
+ @Test
+ public void testUiModeTypeChange_whenDozeMachineIsNotReady_doesNotDoAnything() {
+ when(mDozeMachine.isUninitializedOrFinished()).thenReturn(true);
+
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+
+ verify(mDozeMachine, never()).requestState(any());
+ }
+
+ @Test
+ public void testUiModeTypeChange_CarModeEnabledAndDozeMachineNotReady_suspendsTriggersAfter() {
+ when(mDozeMachine.isUninitializedOrFinished()).thenReturn(true);
+ mDozeSuppressor.onUiModeTypeChanged(UI_MODE_TYPE_CAR);
+ verify(mDozeMachine, never()).requestState(any());
+
+ mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+
+ verify(mDozeMachine, times(1)).requestState(DOZE_SUSPEND_TRIGGERS);
+ }
+
+ @Test
public void testEndDoze_unprovisioned() {
// GIVEN device unprovisioned
when(mDozeHost.isProvisioned()).thenReturn(false);
@@ -276,14 +295,4 @@ public class DozeSuppressorTest extends SysuiTestCase {
verify(mDozeHost).addCallback(mDozeHostCaptor.capture());
mDozeHostCallback = mDozeHostCaptor.getValue();
}
-
- private void captureBroadcastReceiver() {
- verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverCaptor.capture(),
- mIntentFilterCaptor.capture());
- mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
- mIntentFilter = mIntentFilterCaptor.getValue();
- assertEquals(2, mIntentFilter.countActions());
- org.hamcrest.MatcherAssert.assertThat(() -> mIntentFilter.actionsIterator(),
- containsInAnyOrder(ACTION_ENTER_CAR_MODE, ACTION_EXIT_CAR_MODE));
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 781dc1550048..6091d3a93f14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -23,10 +23,10 @@ import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -88,6 +88,8 @@ public class DozeTriggersTest extends SysuiTestCase {
@Mock
private ProximityCheck mProximityCheck;
@Mock
+ private DozeLog mDozeLog;
+ @Mock
private AuthController mAuthController;
@Mock
private UiEventLogger mUiEventLogger;
@@ -127,7 +129,7 @@ public class DozeTriggersTest extends SysuiTestCase {
mTriggers = new DozeTriggers(mContext, mHost, config, dozeParameters,
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
- mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(),
+ mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(),
mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController,
mDevicePostureController);
mTriggers.setDozeMachine(mMachine);
@@ -342,6 +344,16 @@ public class DozeTriggersTest extends SysuiTestCase {
verify(mProximityCheck).destroy();
}
+ @Test
+ public void testIsExecutingTransition_dropPulse() {
+ when(mHost.isPulsePending()).thenReturn(false);
+ when(mMachine.isExecutingTransition()).thenReturn(true);
+
+ mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, 100, 100, null);
+
+ verify(mDozeLog).tracePulseDropped(anyString(), eq(null));
+ }
+
private void waitForSensorManager() {
mExecutor.runAllReady();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
index 522b5b5a8530..50f27ea27ae9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
@@ -33,7 +33,7 @@ import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaCarouselController;
+import com.android.systemui.media.controls.ui.MediaCarouselController;
import com.android.systemui.media.dream.MediaDreamComplication;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
index fc672016a886..65ae90b8f7e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -17,11 +17,19 @@
package com.android.systemui.dump
import androidx.test.filters.SmallTest
+import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
+import com.android.systemui.ProtoDumpable
import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.io.StringWriter
+import javax.inject.Provider
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
@@ -29,7 +37,6 @@ import org.mockito.Mockito.anyInt
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
@SmallTest
class DumpHandlerTest : SysuiTestCase() {
@@ -43,6 +50,8 @@ class DumpHandlerTest : SysuiTestCase() {
@Mock
private lateinit var pw: PrintWriter
+ @Mock
+ private lateinit var fd: FileDescriptor
@Mock
private lateinit var dumpable1: Dumpable
@@ -52,6 +61,11 @@ class DumpHandlerTest : SysuiTestCase() {
private lateinit var dumpable3: Dumpable
@Mock
+ private lateinit var protoDumpable1: ProtoDumpable
+ @Mock
+ private lateinit var protoDumpable2: ProtoDumpable
+
+ @Mock
private lateinit var buffer1: LogBuffer
@Mock
private lateinit var buffer2: LogBuffer
@@ -66,7 +80,9 @@ class DumpHandlerTest : SysuiTestCase() {
mContext,
dumpManager,
logBufferEulogizer,
- mutableMapOf(),
+ mutableMapOf(
+ EmptyCoreStartable::class.java to Provider { EmptyCoreStartable() }
+ ),
exceptionHandlerManager
)
}
@@ -82,7 +98,7 @@ class DumpHandlerTest : SysuiTestCase() {
// WHEN some of them are dumped explicitly
val args = arrayOf("dumpable1", "dumpable3", "buffer2")
- dumpHandler.dump(pw, args)
+ dumpHandler.dump(fd, pw, args)
// THEN only the requested ones have their dump() method called
verify(dumpable1).dump(pw, args)
@@ -101,7 +117,7 @@ class DumpHandlerTest : SysuiTestCase() {
// WHEN that module is dumped
val args = arrayOf("dumpable1")
- dumpHandler.dump(pw, args)
+ dumpHandler.dump(fd, pw, args)
// THEN its dump() method is called
verify(dumpable1).dump(pw, args)
@@ -118,7 +134,7 @@ class DumpHandlerTest : SysuiTestCase() {
// WHEN a critical dump is requested
val args = arrayOf("--dump-priority", "CRITICAL")
- dumpHandler.dump(pw, args)
+ dumpHandler.dump(fd, pw, args)
// THEN all modules are dumped (but no buffers)
verify(dumpable1).dump(pw, args)
@@ -139,7 +155,7 @@ class DumpHandlerTest : SysuiTestCase() {
// WHEN a normal dump is requested
val args = arrayOf("--dump-priority", "NORMAL")
- dumpHandler.dump(pw, args)
+ dumpHandler.dump(fd, pw, args)
// THEN all buffers are dumped (but no modules)
verify(dumpable1, never()).dump(
@@ -154,4 +170,44 @@ class DumpHandlerTest : SysuiTestCase() {
verify(buffer1).dump(pw, 0)
verify(buffer2).dump(pw, 0)
}
-} \ No newline at end of file
+
+ @Test
+ fun testConfigDump() {
+ // GIVEN a StringPrintWriter
+ val stringWriter = StringWriter()
+ val spw = PrintWriter(stringWriter)
+
+ // When a config dump is requested
+ dumpHandler.dump(fd, spw, arrayOf("config"))
+
+ assertThat(stringWriter.toString()).contains(EmptyCoreStartable::class.java.simpleName)
+ }
+
+ @Test
+ fun testDumpAllProtoDumpables() {
+ dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
+ dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
+
+ val args = arrayOf(DumpHandler.PROTO)
+ dumpHandler.dump(fd, pw, args)
+
+ verify(protoDumpable1).dumpProto(any(), eq(args))
+ verify(protoDumpable2).dumpProto(any(), eq(args))
+ }
+
+ @Test
+ fun testDumpSingleProtoDumpable() {
+ dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
+ dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
+
+ val args = arrayOf(DumpHandler.PROTO, "protoDumpable1")
+ dumpHandler.dump(fd, pw, args)
+
+ verify(protoDumpable1).dumpProto(any(), eq(args))
+ verify(protoDumpable2, never()).dumpProto(any(), any())
+ }
+
+ private class EmptyCoreStartable : CoreStartable {
+ override fun start() {}
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
index bd029a727ee3..64547f4463d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
@@ -16,9 +16,9 @@
package com.android.systemui.dump
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogcatEchoTracker
/**
* Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index 4c6113870737..9628ee93ceff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -51,14 +51,6 @@ class FlagCommandTest : SysuiTestCase() {
}
@Test
- fun noOpCommand() {
- cmd.execute(pw, ArrayList())
- Mockito.verify(pw, Mockito.atLeastOnce()).println()
- Mockito.verify(featureFlags).isEnabled(flagA)
- Mockito.verify(featureFlags).isEnabled(flagB)
- }
-
- @Test
fun readFlagCommand() {
cmd.execute(pw, listOf(flagA.id.toString()))
Mockito.verify(featureFlags).isEnabled(flagA)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
deleted file mode 100644
index b5e9e8decb4c..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
+++ /dev/null
@@ -1,152 +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.keyguard;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.AnimatableClockController;
-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.flags.FeatureFlags;
-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;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-import java.util.concurrent.Executor;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AnimatableClockControllerTest extends SysuiTestCase {
- @Mock
- private AnimatableClockView mClockView;
- @Mock
- private StatusBarStateController mStatusBarStateController;
- @Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
- private BatteryController mBatteryController;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private Resources mResources;
- @Mock
- private Executor mMainExecutor;
- @Mock
- private Executor mBgExecutor;
- @Mock
- private FeatureFlags mFeatureFlags;
-
- private MockitoSession mStaticMockSession;
- private AnimatableClockController mAnimatableClockController;
-
- // Capture listeners so that they can be used to send events
- @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
- private View.OnAttachStateChangeListener mAttachListener;
-
- @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor;
- private StatusBarStateController.StateListener mStatusBarStateCallback;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mStaticMockSession = mockitoSession()
- .mockStatic(Utils.class)
- .strictness(Strictness.LENIENT) // it's ok if mocked classes aren't used
- .startMocking();
- when(Utils.getColorAttrDefaultColor(anyObject(), anyInt())).thenReturn(0);
-
- mAnimatableClockController = new AnimatableClockController(
- mClockView,
- mStatusBarStateController,
- mBroadcastDispatcher,
- mBatteryController,
- mKeyguardUpdateMonitor,
- mResources,
- mMainExecutor,
- mBgExecutor,
- mFeatureFlags
- );
- mAnimatableClockController.init();
- captureAttachListener();
- }
-
- @After
- public void tearDown() {
- mStaticMockSession.finishMocking();
- }
-
- @Test
- public void testOnAttachedUpdatesDozeStateToTrue() {
- // GIVEN dozing
- when(mStatusBarStateController.isDozing()).thenReturn(true);
- when(mStatusBarStateController.getDozeAmount()).thenReturn(1f);
-
- // WHEN the clock view gets attached
- mAttachListener.onViewAttachedToWindow(mClockView);
-
- // THEN the clock controller updated its dozing state to true
- assertTrue(mAnimatableClockController.isDozing());
- }
-
- @Test
- public void testOnAttachedUpdatesDozeStateToFalse() {
- // GIVEN not dozing
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
-
- // WHEN the clock view gets attached
- mAttachListener.onViewAttachedToWindow(mClockView);
-
- // THEN the clock controller updated its dozing state to false
- assertFalse(mAnimatableClockController.isDozing());
- }
-
- private void captureAttachListener() {
- verify(mClockView).addOnAttachStateChangeListener(mAttachCaptor.capture());
- mAttachListener = mAttachCaptor.getValue();
- }
-}
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 39f3c96803c3..4c986bffd172 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard;
import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -229,6 +230,28 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
}
@Test
+ public void testBouncerPrompt_nonStrongIdleTimeout() {
+ // GIVEN trust agents enabled and biometrics are enrolled
+ when(mUpdateMonitor.isTrustUsuallyManaged(anyInt())).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricsPossible(anyInt())).thenReturn(true);
+
+ // WHEN the strong auth reason is STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
+ KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker =
+ mock(KeyguardUpdateMonitor.StrongAuthTracker.class);
+ when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(strongAuthTracker);
+ when(strongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
+ when(strongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(
+ anyInt())).thenReturn(false);
+ when(strongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT);
+
+ // THEN the bouncer prompt reason should return
+ // STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
+ assertEquals(KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT,
+ mViewMediator.mViewMediatorCallback.getBouncerPromptReason());
+ }
+
+ @Test
public void testHideSurfaceBehindKeyguardMarksKeyguardNotGoingAway() {
mViewMediator.hideSurfaceBehindKeyguard();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
new file mode 100644
index 000000000000..1b34100b1cef
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.animation.AnimationHandler.AnimationFrameCallbackProvider
+import android.animation.ValueAnimator
+import android.util.Log
+import android.util.Log.TerribleFailure
+import android.util.Log.TerribleFailureHandler
+import android.view.Choreographer.FrameCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import java.math.BigDecimal
+import java.math.RoundingMode
+import java.util.UUID
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardTransitionRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardTransitionRepository
+ private lateinit var oldWtfHandler: TerribleFailureHandler
+ private lateinit var wtfHandler: WtfHandler
+
+ @Before
+ fun setUp() {
+ underTest = KeyguardTransitionRepository()
+ wtfHandler = WtfHandler()
+ oldWtfHandler = Log.setWtfHandler(wtfHandler)
+ }
+
+ @After
+ fun tearDown() {
+ oldWtfHandler?.let { Log.setWtfHandler(it) }
+ }
+
+ @Test
+ fun `startTransition runs animator to completion`() =
+ runBlocking(IMMEDIATE) {
+ val (animator, provider) = setupAnimator(this)
+
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+
+ underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator))
+
+ val startTime = System.currentTimeMillis()
+ while (animator.isRunning()) {
+ yield()
+ if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
+ fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
+ }
+ }
+
+ assertSteps(steps, listWithStep(BigDecimal(.1)))
+
+ job.cancel()
+ provider.stop()
+ }
+
+ @Test
+ fun `startTransition called during another transition fails`() {
+ underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null))
+ underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null))
+
+ assertThat(wtfHandler.failed).isTrue()
+ }
+
+ @Test
+ fun `Null animator enables manual control with updateTransition`() =
+ runBlocking(IMMEDIATE) {
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+
+ val uuid =
+ underTest.startTransition(
+ TransitionInfo(
+ ownerName = OWNER_NAME,
+ from = AOD,
+ to = LOCKSCREEN,
+ animator = null,
+ )
+ )
+
+ checkNotNull(uuid).let {
+ underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
+ underTest.updateTransition(it, 1f, TransitionState.FINISHED)
+ }
+
+ assertThat(steps.size).isEqualTo(3)
+ assertThat(steps[0])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ assertThat(steps[1])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING))
+ assertThat(steps[2])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+ job.cancel()
+ }
+
+ @Test
+ fun `Attempt to manually update transition with invalid UUID throws exception`() {
+ underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING)
+ assertThat(wtfHandler.failed).isTrue()
+ }
+
+ @Test
+ fun `Attempt to manually update transition after FINISHED state throws exception`() {
+ val uuid =
+ underTest.startTransition(
+ TransitionInfo(
+ ownerName = OWNER_NAME,
+ from = AOD,
+ to = LOCKSCREEN,
+ animator = null,
+ )
+ )
+
+ checkNotNull(uuid).let {
+ underTest.updateTransition(it, 1f, TransitionState.FINISHED)
+ underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
+ }
+ assertThat(wtfHandler.failed).isTrue()
+ }
+
+ private fun listWithStep(step: BigDecimal): List<BigDecimal> {
+ val steps = mutableListOf<BigDecimal>()
+
+ var i = BigDecimal.ZERO
+ while (i.compareTo(BigDecimal.ONE) <= 0) {
+ steps.add(i)
+ i = (i + step).setScale(2, RoundingMode.HALF_UP)
+ }
+
+ return steps
+ }
+
+ private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
+ // + 2 accounts for start and finish of automated transition
+ assertThat(steps.size).isEqualTo(fractions.size + 2)
+
+ assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ fractions.forEachIndexed { index, fraction ->
+ assertThat(steps[index + 1])
+ .isEqualTo(
+ TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
+ )
+ }
+ assertThat(steps[steps.size - 1])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+
+ assertThat(wtfHandler.failed).isFalse()
+ }
+
+ private fun setupAnimator(
+ scope: CoroutineScope
+ ): Pair<ValueAnimator, TestFrameCallbackProvider> {
+ val animator =
+ ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(ANIMATION_DURATION)
+ }
+
+ val provider = TestFrameCallbackProvider(animator, scope)
+ provider.start()
+
+ return Pair(animator, provider)
+ }
+
+ /** Gives direct control over ValueAnimator. See [AnimationHandler] */
+ private class TestFrameCallbackProvider(
+ private val animator: ValueAnimator,
+ private val scope: CoroutineScope,
+ ) : AnimationFrameCallbackProvider {
+
+ private var frameCount = 1L
+ private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
+ private var job: Job? = null
+
+ fun start() {
+ animator.getAnimationHandler().setProvider(this)
+
+ job =
+ scope.launch {
+ frames.collect {
+ // Delay is required for AnimationHandler to properly register a callback
+ delay(1)
+ val (frameNumber, callback) = it
+ callback?.doFrame(frameNumber)
+ }
+ }
+ }
+
+ fun stop() {
+ job?.cancel()
+ animator.getAnimationHandler().setProvider(null)
+ }
+
+ override fun postFrameCallback(cb: FrameCallback) {
+ frames.value = Pair(++frameCount, cb)
+ }
+ override fun postCommitCallback(runnable: Runnable) {}
+ override fun getFrameTime() = frameCount
+ override fun getFrameDelay() = 1L
+ override fun setFrameDelay(delay: Long) {}
+ }
+
+ private class WtfHandler : TerribleFailureHandler {
+ var failed = false
+ override fun onTerribleFailure(tag: String, what: TerribleFailure, system: Boolean) {
+ failed = true
+ }
+ }
+
+ companion object {
+ private const val MAX_TEST_DURATION = 100L
+ private const val ANIMATION_DURATION = 10L
+ private const val OWNER_NAME = "Test"
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
deleted file mode 100644
index f34c2ac57a5c..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ /dev/null
@@ -1,426 +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.media
-
-import android.app.PendingIntent
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.InstanceId
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
-import com.android.systemui.media.MediaCarouselController.Companion.DURATION
-import com.android.systemui.media.MediaCarouselController.Companion.PAGINATION_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.TRANSFORM_BEZIER
-import com.android.systemui.media.MediaHierarchyManager.Companion.LOCATION_QS
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.time.FakeSystemClock
-import javax.inject.Provider
-import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertTrue
-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.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-private val DATA = MediaTestUtils.emptyMediaData
-
-private val SMARTSPACE_KEY = "smartspace"
-
-@SmallTest
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
-class MediaCarouselControllerTest : SysuiTestCase() {
-
- @Mock lateinit var mediaControlPanelFactory: Provider<MediaControlPanel>
- @Mock lateinit var panel: MediaControlPanel
- @Mock lateinit var visualStabilityProvider: VisualStabilityProvider
- @Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
- @Mock lateinit var mediaHostState: MediaHostState
- @Mock lateinit var activityStarter: ActivityStarter
- @Mock @Main private lateinit var executor: DelayableExecutor
- @Mock lateinit var mediaDataManager: MediaDataManager
- @Mock lateinit var configurationController: ConfigurationController
- @Mock lateinit var falsingCollector: FalsingCollector
- @Mock lateinit var falsingManager: FalsingManager
- @Mock lateinit var dumpManager: DumpManager
- @Mock lateinit var logger: MediaUiEventLogger
- @Mock lateinit var debugLogger: MediaCarouselControllerLogger
- @Mock lateinit var mediaPlayer: MediaControlPanel
- @Mock lateinit var mediaViewController: MediaViewController
- @Mock lateinit var smartspaceMediaData: SmartspaceMediaData
- @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
- @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
-
- private val clock = FakeSystemClock()
- private lateinit var mediaCarouselController: MediaCarouselController
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- mediaCarouselController = MediaCarouselController(
- context,
- mediaControlPanelFactory,
- visualStabilityProvider,
- mediaHostStatesManager,
- activityStarter,
- clock,
- executor,
- mediaDataManager,
- configurationController,
- falsingCollector,
- falsingManager,
- dumpManager,
- logger,
- debugLogger
- )
- verify(mediaDataManager).addListener(capture(listener))
- verify(visualStabilityProvider)
- .addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
- whenever(mediaControlPanelFactory.get()).thenReturn(mediaPlayer)
- whenever(mediaPlayer.mediaViewController).thenReturn(mediaViewController)
- whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
- MediaPlayerData.clear()
- }
-
- @Test
- fun testPlayerOrdering() {
- // Test values: key, data, last active time
- val playingLocal = Triple("playing local",
- DATA.copy(active = true, isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false),
- 4500L)
-
- val playingCast = Triple("playing cast",
- DATA.copy(active = true, isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, resumption = false),
- 5000L)
-
- val pausedLocal = Triple("paused local",
- DATA.copy(active = true, isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false),
- 1000L)
-
- val pausedCast = Triple("paused cast",
- DATA.copy(active = true, isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, resumption = false),
- 2000L)
-
- val playingRcn = Triple("playing RCN",
- DATA.copy(active = true, isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, resumption = false),
- 5000L)
-
- val pausedRcn = Triple("paused RCN",
- DATA.copy(active = true, isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, resumption = false),
- 5000L)
-
- val active = Triple("active",
- DATA.copy(active = true, isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true),
- 250L)
-
- val resume1 = Triple("resume 1",
- DATA.copy(active = false, isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true),
- 500L)
-
- val resume2 = Triple("resume 2",
- DATA.copy(active = false, isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true),
- 1000L)
-
- val activeMoreRecent = Triple("active more recent",
- DATA.copy(active = false, isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true, lastActive = 2L),
- 1000L)
-
- val activeLessRecent = Triple("active less recent",
- DATA.copy(active = false, isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true, lastActive = 1L),
- 1000L)
- // Expected ordering for media players:
- // Actively playing local sessions
- // Actively playing cast sessions
- // Paused local and cast sessions, by last active
- // RCNs
- // Resume controls, by last active
-
- val expected = listOf(playingLocal, playingCast, pausedCast, pausedLocal, playingRcn,
- pausedRcn, active, resume2, resume1)
-
- expected.forEach {
- clock.setCurrentTimeMillis(it.third)
- MediaPlayerData.addMediaPlayer(it.first, it.second.copy(notificationKey = it.first),
- panel, clock, isSsReactivated = false)
- }
-
- for ((index, key) in MediaPlayerData.playerKeys().withIndex()) {
- assertEquals(expected.get(index).first, key.data.notificationKey)
- }
- }
-
- @Test
- fun testOrderWithSmartspace_prioritized() {
- testPlayerOrdering()
-
- // If smartspace is prioritized
- MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
- true, clock)
-
- // Then it should be shown immediately after any actively playing controls
- assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
- }
-
- @Test
- fun testOrderWithSmartspace_notPrioritized() {
- testPlayerOrdering()
-
- // If smartspace is not prioritized
- MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
- false, clock)
-
- // Then it should be shown at the end of the carousel's active entries
- val idx = MediaPlayerData.playerKeys().count { it.data.active } - 1
- assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
- }
-
- @Test
- fun testSwipeDismiss_logged() {
- mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
-
- verify(logger).logSwipeDismiss()
- }
-
- @Test
- fun testSettingsButton_logged() {
- mediaCarouselController.settingsButton.callOnClick()
-
- verify(logger).logCarouselSettings()
- }
-
- @Test
- fun testLocationChangeQs_logged() {
- mediaCarouselController.onDesiredLocationChanged(
- MediaHierarchyManager.LOCATION_QS,
- mediaHostState,
- animate = false)
- verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
- }
-
- @Test
- fun testLocationChangeQqs_logged() {
- mediaCarouselController.onDesiredLocationChanged(
- MediaHierarchyManager.LOCATION_QQS,
- mediaHostState,
- animate = false)
- verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
- }
-
- @Test
- fun testLocationChangeLockscreen_logged() {
- mediaCarouselController.onDesiredLocationChanged(
- MediaHierarchyManager.LOCATION_LOCKSCREEN,
- mediaHostState,
- animate = false)
- verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
- }
-
- @Test
- fun testLocationChangeDream_logged() {
- mediaCarouselController.onDesiredLocationChanged(
- MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
- mediaHostState,
- animate = false)
- verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
- }
-
- @Test
- fun testRecommendationRemoved_logged() {
- val packageName = "smartspace package"
- val instanceId = InstanceId.fakeInstanceId(123)
-
- val smartspaceData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- packageName = packageName,
- instanceId = instanceId
- )
- MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, smartspaceData, panel, true, clock)
- mediaCarouselController.removePlayer(SMARTSPACE_KEY)
-
- verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
- }
-
- fun testMediaLoaded_ScrollToActivePlayer() {
- listener.value.onMediaDataLoaded("playing local",
- null,
- DATA.copy(active = true, isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)
- )
- listener.value.onMediaDataLoaded("paused local",
- null,
- DATA.copy(active = true, isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false))
- // adding a media recommendation card.
- MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
- false, clock)
- mediaCarouselController.shouldScrollToActivePlayer = true
- // switching between media players.
- listener.value.onMediaDataLoaded("playing local",
- "playing local",
- DATA.copy(active = true, isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true)
- )
- listener.value.onMediaDataLoaded("paused local",
- "paused local",
- DATA.copy(active = true, isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false))
-
- assertEquals(
- MediaPlayerData.getMediaPlayerIndex("paused local"),
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
- )
- }
-
- @Test
- fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
- MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
- false, clock)
- listener.value.onMediaDataLoaded("playing local",
- null,
- DATA.copy(active = true, isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)
- )
-
- var playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
- assertEquals(
- playerIndex,
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
- )
- assertEquals(playerIndex, 0)
-
- // Replaying the same media player one more time.
- // And check that the card stays in its position.
- listener.value.onMediaDataLoaded("playing local",
- null,
- DATA.copy(active = true, isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)
- )
- playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
- assertEquals(playerIndex, 0)
- }
-
- @Test
- fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
- var result = false
- mediaCarouselController.updateHostVisibility = { result = true }
-
- whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
- listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
-
- assertEquals(true, result)
- }
-
- @Test
- fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
- var result = false
- mediaCarouselController.updateHostVisibility = { result = true }
-
- whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
- listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
- assertEquals(false, result)
-
- visualStabilityCallback.value.onReorderingAllowed()
- assertEquals(true, result)
- }
-
- @Test
- fun testGetCurrentVisibleMediaContentIntent() {
- val clickIntent1 = mock(PendingIntent::class.java)
- val player1 = Triple("player1",
- DATA.copy(clickIntent = clickIntent1),
- 1000L)
- clock.setCurrentTimeMillis(player1.third)
- MediaPlayerData.addMediaPlayer(player1.first,
- player1.second.copy(notificationKey = player1.first),
- panel, clock, isSsReactivated = false)
-
- assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1)
-
- val clickIntent2 = mock(PendingIntent::class.java)
- val player2 = Triple("player2",
- DATA.copy(clickIntent = clickIntent2),
- 2000L)
- clock.setCurrentTimeMillis(player2.third)
- MediaPlayerData.addMediaPlayer(player2.first,
- player2.second.copy(notificationKey = player2.first),
- panel, clock, isSsReactivated = false)
-
- // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
- // added to the front because it was active more recently.
- assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
-
- val clickIntent3 = mock(PendingIntent::class.java)
- val player3 = Triple("player3",
- DATA.copy(clickIntent = clickIntent3),
- 500L)
- clock.setCurrentTimeMillis(player3.third)
- MediaPlayerData.addMediaPlayer(player3.first,
- player3.second.copy(notificationKey = player3.first),
- panel, clock, isSsReactivated = false)
-
- // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
- // added to the end because it was active less recently.
- assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
- }
-
- @Test
- fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
- val delta = 0.0001F
- val paginationSquishMiddle = TRANSFORM_BEZIER.getInterpolation(
- (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION)
- val paginationSquishEnd = TRANSFORM_BEZIER.getInterpolation(
- (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION)
- whenever(mediaHostStatesManager.mediaHostStates)
- .thenReturn(mutableMapOf(LOCATION_QS to mediaHostState))
- whenever(mediaHostState.visible).thenReturn(true)
- mediaCarouselController.currentEndLocation = LOCATION_QS
- whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle)
- mediaCarouselController.updatePageIndicatorAlpha()
- assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta)
-
- whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd)
- mediaCarouselController.updatePageIndicatorAlpha()
- assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
deleted file mode 100644
index 3d9ed5fe7f7b..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.android.systemui.media
-
-import com.android.internal.logging.InstanceId
-
-class MediaTestUtils {
- companion object {
- val emptyMediaData = MediaData(
- userId = 0,
- initialized = true,
- app = null,
- appIcon = null,
- artist = null,
- song = null,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
- packageName = "",
- token = null,
- clickIntent = null,
- device = null,
- active = true,
- resumeAction = null,
- isPlaying = false,
- instanceId = InstanceId.fakeInstanceId(-1),
- appUid = -1)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewHolderTest.kt
deleted file mode 100644
index ee327937e091..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewHolderTest.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.android.systemui.media
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.LayoutInflater
-import android.widget.FrameLayout
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class MediaViewHolderTest : SysuiTestCase() {
-
- @Test
- fun create_succeeds() {
- val inflater = LayoutInflater.from(context)
- val parent = FrameLayout(context)
-
- MediaViewHolder.create(inflater, parent)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt
new file mode 100644
index 000000000000..3437365d9902
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls
+
+import com.android.internal.logging.InstanceId
+import com.android.systemui.media.controls.models.player.MediaData
+
+class MediaTestUtils {
+ companion object {
+ val emptyMediaData =
+ MediaData(
+ userId = 0,
+ initialized = true,
+ app = null,
+ appIcon = null,
+ artist = null,
+ song = null,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = "",
+ token = null,
+ clickIntent = null,
+ device = null,
+ active = true,
+ resumeAction = null,
+ isPlaying = false,
+ instanceId = InstanceId.fakeInstanceId(-1),
+ appUid = -1
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt
new file mode 100644
index 000000000000..c829d4cbfb71
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.controls.models.player
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class MediaViewHolderTest : SysuiTestCase() {
+
+ @Test
+ fun create_succeeds() {
+ val inflater = LayoutInflater.from(context)
+ val parent = FrameLayout(context)
+
+ MediaViewHolder.create(inflater, parent)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
index 9e9cda843c8f..97b18e214550 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.player
import android.animation.Animator
import android.animation.ObjectAnimator
@@ -26,6 +26,7 @@ import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.ui.SquigglyProgress
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -33,8 +34,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -56,10 +57,14 @@ class SeekBarObserverTest : SysuiTestCase() {
@Before
fun setUp() {
- context.orCreateTestableResources
- .addOverride(R.dimen.qs_media_enabled_seekbar_height, enabledHeight)
- context.orCreateTestableResources
- .addOverride(R.dimen.qs_media_disabled_seekbar_height, disabledHeight)
+ context.orCreateTestableResources.addOverride(
+ R.dimen.qs_media_enabled_seekbar_height,
+ enabledHeight
+ )
+ context.orCreateTestableResources.addOverride(
+ R.dimen.qs_media_disabled_seekbar_height,
+ disabledHeight
+ )
seekBarView = SeekBar(context)
seekBarView.progressDrawable = mockSquigglyProgress
@@ -69,11 +74,12 @@ class SeekBarObserverTest : SysuiTestCase() {
whenever(mockHolder.scrubbingElapsedTimeView).thenReturn(scrubbingElapsedTimeView)
whenever(mockHolder.scrubbingTotalTimeView).thenReturn(scrubbingTotalTimeView)
- observer = object : SeekBarObserver(mockHolder) {
- override fun buildResetAnimator(targetTime: Int): Animator {
- return mockSeekbarAnimator
+ observer =
+ object : SeekBarObserver(mockHolder) {
+ override fun buildResetAnimator(targetTime: Int): Animator {
+ return mockSeekbarAnimator
+ }
}
- }
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
index 597334033895..7cd8e749a6e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.models.player
import android.media.MediaMetadata
import android.media.session.MediaController
@@ -57,17 +57,18 @@ public class SeekBarViewModelTest : SysuiTestCase() {
private lateinit var viewModel: SeekBarViewModel
private lateinit var fakeExecutor: FakeExecutor
- private val taskExecutor: TaskExecutor = object : TaskExecutor() {
- override fun executeOnDiskIO(runnable: Runnable) {
- runnable.run()
- }
- override fun postToMainThread(runnable: Runnable) {
- runnable.run()
- }
- override fun isMainThread(): Boolean {
- return true
+ private val taskExecutor: TaskExecutor =
+ object : TaskExecutor() {
+ override fun executeOnDiskIO(runnable: Runnable) {
+ runnable.run()
+ }
+ override fun postToMainThread(runnable: Runnable) {
+ runnable.run()
+ }
+ override fun isMainThread(): Boolean {
+ return true
+ }
}
- }
@Mock private lateinit var mockController: MediaController
@Mock private lateinit var mockTransport: MediaController.TransportControls
@Mock private lateinit var falsingManager: FalsingManager
@@ -81,7 +82,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
fun setUp() {
fakeExecutor = FakeExecutor(FakeSystemClock())
viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor), falsingManager)
- viewModel.logSeek = { }
+ viewModel.logSeek = {}
whenever(mockController.sessionToken).thenReturn(token1)
whenever(mockBar.context).thenReturn(context)
@@ -135,16 +136,18 @@ public class SeekBarViewModelTest : SysuiTestCase() {
fun updateDurationWithPlayback() {
// GIVEN that the duration is contained within the metadata
val duration = 12000L
- val metadata = MediaMetadata.Builder().run {
- putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
- build()
- }
+ val metadata =
+ MediaMetadata.Builder().run {
+ putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+ build()
+ }
whenever(mockController.getMetadata()).thenReturn(metadata)
// AND a valid playback state (ie. media session is not destroyed)
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 200L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
@@ -158,10 +161,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
fun updateDurationWithoutPlayback() {
// GIVEN that the duration is contained within the metadata
val duration = 12000L
- val metadata = MediaMetadata.Builder().run {
- putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
- build()
- }
+ val metadata =
+ MediaMetadata.Builder().run {
+ putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+ build()
+ }
whenever(mockController.getMetadata()).thenReturn(metadata)
// WHEN the controller is updated
viewModel.updateController(mockController)
@@ -174,16 +178,18 @@ public class SeekBarViewModelTest : SysuiTestCase() {
fun updateDurationNegative() {
// GIVEN that the duration is negative
val duration = -1L
- val metadata = MediaMetadata.Builder().run {
- putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
- build()
- }
+ val metadata =
+ MediaMetadata.Builder().run {
+ putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+ build()
+ }
whenever(mockController.getMetadata()).thenReturn(metadata)
// AND a valid playback state (ie. media session is not destroyed)
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 200L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
@@ -195,16 +201,18 @@ public class SeekBarViewModelTest : SysuiTestCase() {
fun updateDurationZero() {
// GIVEN that the duration is zero
val duration = 0L
- val metadata = MediaMetadata.Builder().run {
- putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
- build()
- }
+ val metadata =
+ MediaMetadata.Builder().run {
+ putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+ build()
+ }
whenever(mockController.getMetadata()).thenReturn(metadata)
// AND a valid playback state (ie. media session is not destroyed)
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 200L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
@@ -218,10 +226,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
// GIVEN that the metadata is null
whenever(mockController.getMetadata()).thenReturn(null)
// AND a valid playback state (ie. media session is not destroyed)
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 200L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
@@ -233,10 +242,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
fun updateElapsedTime() {
// GIVEN that the PlaybackState contains the current position
val position = 200L
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, position, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, position, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
@@ -248,10 +258,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
@Ignore
fun updateSeekAvailable() {
// GIVEN that seek is included in actions
- val state = PlaybackState.Builder().run {
- setActions(PlaybackState.ACTION_SEEK_TO)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setActions(PlaybackState.ACTION_SEEK_TO)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
@@ -263,10 +274,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
@Ignore
fun updateSeekNotAvailable() {
// GIVEN that seek is not included in actions
- val state = PlaybackState.Builder().run {
- setActions(PlaybackState.ACTION_PLAY)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setActions(PlaybackState.ACTION_PLAY)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
@@ -318,9 +330,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
@Ignore
fun onSeekProgressWithSeekStarting() {
val pos = 42L
- with(viewModel) {
- onSeekProgress(pos)
- }
+ with(viewModel) { onSeekProgress(pos) }
fakeExecutor.runAllReady()
// THEN then elapsed time should not be updated
assertThat(viewModel.progress.value!!.elapsedTime).isNull()
@@ -329,11 +339,12 @@ public class SeekBarViewModelTest : SysuiTestCase() {
@Test
fun seekStarted_listenerNotified() {
var isScrubbing: Boolean? = null
- val listener = object : SeekBarViewModel.ScrubbingChangeListener {
- override fun onScrubbingChanged(scrubbing: Boolean) {
- isScrubbing = scrubbing
+ val listener =
+ object : SeekBarViewModel.ScrubbingChangeListener {
+ override fun onScrubbingChanged(scrubbing: Boolean) {
+ isScrubbing = scrubbing
+ }
}
- }
viewModel.setScrubbingChangeListener(listener)
viewModel.onSeekStarting()
@@ -345,11 +356,12 @@ public class SeekBarViewModelTest : SysuiTestCase() {
@Test
fun seekEnded_listenerNotified() {
var isScrubbing: Boolean? = null
- val listener = object : SeekBarViewModel.ScrubbingChangeListener {
- override fun onScrubbingChanged(scrubbing: Boolean) {
- isScrubbing = scrubbing
+ val listener =
+ object : SeekBarViewModel.ScrubbingChangeListener {
+ override fun onScrubbingChanged(scrubbing: Boolean) {
+ isScrubbing = scrubbing
+ }
}
- }
viewModel.setScrubbingChangeListener(listener)
// Start seeking
@@ -385,9 +397,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
val bar = SeekBar(context)
// WHEN we get an onProgressChanged event without an onStartTrackingTouch event
- with(viewModel.seekBarListener) {
- onProgressChanged(bar, pos, true)
- }
+ with(viewModel.seekBarListener) { onProgressChanged(bar, pos, true) }
fakeExecutor.runAllReady()
// THEN we immediately update the transport
@@ -412,9 +422,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
viewModel.updateController(mockController)
// WHEN user starts dragging the seek bar
val pos = 42
- val bar = SeekBar(context).apply {
- progress = pos
- }
+ val bar = SeekBar(context).apply { progress = pos }
viewModel.seekBarListener.onStartTrackingTouch(bar)
fakeExecutor.runAllReady()
// THEN transport controls should be used
@@ -427,9 +435,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
viewModel.updateController(mockController)
// WHEN user ends drag
val pos = 42
- val bar = SeekBar(context).apply {
- progress = pos
- }
+ val bar = SeekBar(context).apply { progress = pos }
viewModel.seekBarListener.onStopTrackingTouch(bar)
fakeExecutor.runAllReady()
// THEN transport controls should be used
@@ -443,9 +449,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
// WHEN user starts dragging the seek bar
val pos = 42
val progPos = 84
- val bar = SeekBar(context).apply {
- progress = pos
- }
+ val bar = SeekBar(context).apply { progress = pos }
with(viewModel.seekBarListener) {
onStartTrackingTouch(bar)
onProgressChanged(bar, progPos, true)
@@ -478,10 +482,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
@Test
fun queuePollTaskWhenPlaying() {
// GIVEN that the track is playing
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 100L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 100L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
@@ -492,10 +497,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
@Test
fun noQueuePollTaskWhenStopped() {
// GIVEN that the playback state is stopped
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_STOPPED, 200L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_STOPPED, 200L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN updated
viewModel.updateController(mockController)
@@ -512,10 +518,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
runAllReady()
}
// AND the playback state is playing
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 200L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN updated
viewModel.updateController(mockController)
@@ -532,10 +539,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
runAllReady()
}
// AND the playback state is playing
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 200L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN updated
viewModel.updateController(mockController)
@@ -546,10 +554,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
@Test
fun pollTaskQueuesAnotherPollTaskWhenPlaying() {
// GIVEN that the track is playing
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 100L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 100L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
viewModel.updateController(mockController)
// WHEN the next task runs
@@ -566,10 +575,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
// GIVEN listening
viewModel.listening = true
// AND the playback state is playing
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 200L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
viewModel.updateController(mockController)
with(fakeExecutor) {
@@ -592,10 +602,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
// GIVEN listening
viewModel.listening = true
// AND the playback state is playing
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 200L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
viewModel.updateController(mockController)
with(fakeExecutor) {
@@ -621,10 +632,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
// GIVEN listening
viewModel.listening = true
// AND the playback state is playing
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 200L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
viewModel.updateController(mockController)
with(fakeExecutor) {
@@ -654,10 +666,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
runAllReady()
}
// AND the playback state is playing
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_STOPPED, 200L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_STOPPED, 200L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
viewModel.updateController(mockController)
// WHEN start listening
@@ -673,10 +686,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
verify(mockController).registerCallback(captor.capture())
val callback = captor.value
// WHEN the callback receives an new state
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 100L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 100L, 1f)
+ build()
+ }
callback.onPlaybackStateChanged(state)
with(fakeExecutor) {
advanceClockToNext()
@@ -690,16 +704,18 @@ public class SeekBarViewModelTest : SysuiTestCase() {
@Ignore
fun clearSeekBar() {
// GIVEN that the duration is contained within the metadata
- val metadata = MediaMetadata.Builder().run {
- putLong(MediaMetadata.METADATA_KEY_DURATION, 12000L)
- build()
- }
+ val metadata =
+ MediaMetadata.Builder().run {
+ putLong(MediaMetadata.METADATA_KEY_DURATION, 12000L)
+ build()
+ }
whenever(mockController.getMetadata()).thenReturn(metadata)
// AND a valid playback state (ie. media session is not destroyed)
- val state = PlaybackState.Builder().run {
- setState(PlaybackState.STATE_PLAYING, 200L, 1f)
- build()
- }
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+ build()
+ }
whenever(mockController.getPlaybackState()).thenReturn(state)
// AND the controller has been updated
viewModel.updateController(mockController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
index b5078bc37b84..1d6e980bdb86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.media
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.controls.models.recommendation
import android.app.smartspace.SmartspaceAction
import android.graphics.drawable.Icon
@@ -36,11 +52,11 @@ class SmartspaceMediaDataTest : SysuiTestCase() {
@Test
fun isValid_tooFewRecs_returnsFalse() {
- val data = DEFAULT_DATA.copy(
- recommendations = listOf(
- SmartspaceAction.Builder("id", "title").setIcon(icon).build()
+ val data =
+ DEFAULT_DATA.copy(
+ recommendations =
+ listOf(SmartspaceAction.Builder("id", "title").setIcon(icon).build())
)
- )
assertThat(data.isValid()).isFalse()
}
@@ -50,14 +66,10 @@ class SmartspaceMediaDataTest : SysuiTestCase() {
val recommendations = mutableListOf<SmartspaceAction>()
// Add one fewer recommendation w/ icon than the number required
for (i in 1 until NUM_REQUIRED_RECOMMENDATIONS) {
- recommendations.add(
- SmartspaceAction.Builder("id", "title").setIcon(icon).build()
- )
+ recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(icon).build())
}
for (i in 1 until 3) {
- recommendations.add(
- SmartspaceAction.Builder("id", "title").setIcon(null).build()
- )
+ recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(null).build())
}
val data = DEFAULT_DATA.copy(recommendations = recommendations)
@@ -70,9 +82,7 @@ class SmartspaceMediaDataTest : SysuiTestCase() {
val recommendations = mutableListOf<SmartspaceAction>()
// Add the number of required recommendations
for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS) {
- recommendations.add(
- SmartspaceAction.Builder("id", "title").setIcon(icon).build()
- )
+ recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(icon).build())
}
val data = DEFAULT_DATA.copy(recommendations = recommendations)
@@ -85,9 +95,7 @@ class SmartspaceMediaDataTest : SysuiTestCase() {
val recommendations = mutableListOf<SmartspaceAction>()
// Add more than enough recommendations
for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS + 3) {
- recommendations.add(
- SmartspaceAction.Builder("id", "title").setIcon(icon).build()
- )
+ recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(icon).build())
}
val data = DEFAULT_DATA.copy(recommendations = recommendations)
@@ -96,13 +104,14 @@ class SmartspaceMediaDataTest : SysuiTestCase() {
}
}
-private val DEFAULT_DATA = SmartspaceMediaData(
- targetId = "INVALID",
- isActive = false,
- packageName = "INVALID",
- cardAction = null,
- recommendations = emptyList(),
- dismissIntent = null,
- headphoneConnectionTimeMillis = 0,
- instanceId = InstanceId.fakeInstanceId(-1)
-)
+private val DEFAULT_DATA =
+ SmartspaceMediaData(
+ targetId = "INVALID",
+ isActive = false,
+ packageName = "INVALID",
+ cardAction = null,
+ recommendations = emptyList(),
+ dismissIntent = null,
+ headphoneConnectionTimeMillis = 0,
+ instanceId = InstanceId.fakeInstanceId(-1)
+ )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
index 04b93d79f83b..4d2d0f05b76a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media;
+package com.android.systemui.media.controls.pipeline;
import static com.google.common.truth.Truth.assertThat;
@@ -26,7 +26,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import android.graphics.Color;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -34,6 +33,8 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.controls.models.player.MediaData;
+import com.android.systemui.media.controls.models.player.MediaDeviceData;
import org.junit.Before;
import org.junit.Rule;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 6468fe1a81d7..575b1c6b126e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
import android.app.smartspace.SmartspaceAction
import android.testing.AndroidTestingRunner
@@ -24,6 +24,11 @@ import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.media.controls.MediaTestUtils
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.ui.MediaPlayerData
+import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -58,24 +63,15 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!!
@TestableLooper.RunWithLooper
class MediaDataFilterTest : SysuiTestCase() {
- @Mock
- private lateinit var listener: MediaDataManager.Listener
- @Mock
- private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock
- private lateinit var broadcastSender: BroadcastSender
- @Mock
- private lateinit var mediaDataManager: MediaDataManager
- @Mock
- private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
- @Mock
- private lateinit var executor: Executor
- @Mock
- private lateinit var smartspaceData: SmartspaceMediaData
- @Mock
- private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction
- @Mock
- private lateinit var logger: MediaUiEventLogger
+ @Mock private lateinit var listener: MediaDataManager.Listener
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var broadcastSender: BroadcastSender
+ @Mock private lateinit var mediaDataManager: MediaDataManager
+ @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
+ @Mock private lateinit var executor: Executor
+ @Mock private lateinit var smartspaceData: SmartspaceMediaData
+ @Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction
+ @Mock private lateinit var logger: MediaUiEventLogger
private lateinit var mediaDataFilter: MediaDataFilter
private lateinit var dataMain: MediaData
@@ -86,14 +82,16 @@ class MediaDataFilterTest : SysuiTestCase() {
fun setup() {
MockitoAnnotations.initMocks(this)
MediaPlayerData.clear()
- mediaDataFilter = MediaDataFilter(
- context,
- broadcastDispatcher,
- broadcastSender,
- lockscreenUserManager,
- executor,
- clock,
- logger)
+ mediaDataFilter =
+ MediaDataFilter(
+ context,
+ broadcastDispatcher,
+ broadcastSender,
+ lockscreenUserManager,
+ executor,
+ clock,
+ logger
+ )
mediaDataFilter.mediaDataManager = mediaDataManager
mediaDataFilter.addListener(listener)
@@ -101,11 +99,13 @@ class MediaDataFilterTest : SysuiTestCase() {
setUser(USER_MAIN)
// Set up test media data
- dataMain = MediaTestUtils.emptyMediaData.copy(
+ dataMain =
+ MediaTestUtils.emptyMediaData.copy(
userId = USER_MAIN,
packageName = PACKAGE,
instanceId = INSTANCE_ID,
- appUid = APP_UID)
+ appUid = APP_UID
+ )
dataGuest = dataMain.copy(userId = USER_GUEST)
`when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
@@ -113,8 +113,8 @@ class MediaDataFilterTest : SysuiTestCase() {
`when`(smartspaceData.isValid()).thenReturn(true)
`when`(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE)
`when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem))
- `when`(smartspaceData.headphoneConnectionTimeMillis).thenReturn(
- clock.currentTimeMillis() - 100)
+ `when`(smartspaceData.headphoneConnectionTimeMillis)
+ .thenReturn(clock.currentTimeMillis() - 100)
`when`(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID)
}
@@ -130,8 +130,8 @@ class MediaDataFilterTest : SysuiTestCase() {
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
// THEN we should tell the listener
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true),
- eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false))
}
@Test
@@ -140,8 +140,8 @@ class MediaDataFilterTest : SysuiTestCase() {
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
// THEN we should NOT tell the listener
- verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(),
- anyInt(), anyBoolean())
+ verify(listener, never())
+ .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@Test
@@ -187,12 +187,12 @@ class MediaDataFilterTest : SysuiTestCase() {
setUser(USER_GUEST)
// THEN we should add back the guest user media
- verify(listener).onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true),
- eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false))
// but not the main user's
- verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), eq(dataMain), anyBoolean(),
- anyInt(), anyBoolean())
+ verify(listener, never())
+ .onMediaDataLoaded(eq(KEY), any(), eq(dataMain), anyBoolean(), anyInt(), anyBoolean())
}
@Test
@@ -340,7 +340,7 @@ class MediaDataFilterTest : SysuiTestCase() {
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
verify(listener)
- .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
@@ -353,8 +353,8 @@ class MediaDataFilterTest : SysuiTestCase() {
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(),
- anyInt(), anyBoolean())
+ verify(listener, never())
+ .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
@@ -370,7 +370,7 @@ class MediaDataFilterTest : SysuiTestCase() {
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
verify(listener)
- .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
@@ -400,15 +400,15 @@ class MediaDataFilterTest : SysuiTestCase() {
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
- eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
// AND we get a smartspace signal
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should tell listeners to treat the media as not active instead
- verify(listener, never()).onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(),
- anyInt(), anyBoolean())
+ verify(listener, never())
+ .onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean())
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
@@ -423,16 +423,23 @@ class MediaDataFilterTest : SysuiTestCase() {
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
- eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
// AND we get a smartspace signal
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should tell listeners to treat the media as active instead
val dataCurrentAndActive = dataCurrent.copy(active = true)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
- eq(100), eq(true))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ eq(dataCurrentAndActive),
+ eq(true),
+ eq(100),
+ eq(true)
+ )
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
// Smartspace update shouldn't be propagated for the empty rec list.
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
@@ -445,20 +452,27 @@ class MediaDataFilterTest : SysuiTestCase() {
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
- eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
// AND we get a smartspace signal
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should tell listeners to treat the media as active instead
val dataCurrentAndActive = dataCurrent.copy(active = true)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
- eq(100), eq(true))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ eq(dataCurrentAndActive),
+ eq(true),
+ eq(100),
+ eq(true)
+ )
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
// Smartspace update should also be propagated but not prioritized.
verify(listener)
- .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
}
@@ -477,14 +491,21 @@ class MediaDataFilterTest : SysuiTestCase() {
fun testOnSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBoth() {
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
- eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
val dataCurrentAndActive = dataCurrent.copy(active = true)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
- eq(100), eq(true))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ eq(dataCurrentAndActive),
+ eq(true),
+ eq(100),
+ eq(true)
+ )
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index f9c7d2d5cb41..11eb26b1da02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.media
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.controls.pipeline
import android.app.Notification
import android.app.Notification.MediaStyle
@@ -26,6 +42,13 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
+import com.android.systemui.media.controls.resume.MediaResumeListener
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.tuner.TunerService
@@ -111,58 +134,68 @@ class MediaDataManagerTest : SysuiTestCase() {
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
- private val originalSmartspaceSetting = Settings.Secure.getInt(context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1)
+ private val originalSmartspaceSetting =
+ Settings.Secure.getInt(
+ context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+ 1
+ )
@Before
fun setup() {
foregroundExecutor = FakeExecutor(clock)
backgroundExecutor = FakeExecutor(clock)
smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
- Settings.Secure.putInt(context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1)
- mediaDataManager = MediaDataManager(
- context = context,
- backgroundExecutor = backgroundExecutor,
- foregroundExecutor = foregroundExecutor,
- mediaControllerFactory = mediaControllerFactory,
- broadcastDispatcher = broadcastDispatcher,
- dumpManager = dumpManager,
- mediaTimeoutListener = mediaTimeoutListener,
- mediaResumeListener = mediaResumeListener,
- mediaSessionBasedFilter = mediaSessionBasedFilter,
- mediaDeviceManager = mediaDeviceManager,
- mediaDataCombineLatest = mediaDataCombineLatest,
- mediaDataFilter = mediaDataFilter,
- activityStarter = activityStarter,
- smartspaceMediaDataProvider = smartspaceMediaDataProvider,
- useMediaResumption = true,
- useQsMediaPlayer = true,
- systemClock = clock,
- tunerService = tunerService,
- mediaFlags = mediaFlags,
- logger = logger
+ Settings.Secure.putInt(
+ context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+ 1
)
- verify(tunerService).addTunable(capture(tunableCaptor),
- eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
+ mediaDataManager =
+ MediaDataManager(
+ context = context,
+ backgroundExecutor = backgroundExecutor,
+ foregroundExecutor = foregroundExecutor,
+ mediaControllerFactory = mediaControllerFactory,
+ broadcastDispatcher = broadcastDispatcher,
+ dumpManager = dumpManager,
+ mediaTimeoutListener = mediaTimeoutListener,
+ mediaResumeListener = mediaResumeListener,
+ mediaSessionBasedFilter = mediaSessionBasedFilter,
+ mediaDeviceManager = mediaDeviceManager,
+ mediaDataCombineLatest = mediaDataCombineLatest,
+ mediaDataFilter = mediaDataFilter,
+ activityStarter = activityStarter,
+ smartspaceMediaDataProvider = smartspaceMediaDataProvider,
+ useMediaResumption = true,
+ useQsMediaPlayer = true,
+ systemClock = clock,
+ tunerService = tunerService,
+ mediaFlags = mediaFlags,
+ logger = logger
+ )
+ verify(tunerService)
+ .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
session = MediaSession(context, "MediaDataManagerTestSession")
- mediaNotification = SbnBuilder().run {
- setPkg(PACKAGE_NAME)
- modifyNotification(context).also {
- it.setSmallIcon(android.R.drawable.ic_media_pause)
- it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ mediaNotification =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ }
+ build()
+ }
+ metadataBuilder =
+ MediaMetadata.Builder().apply {
+ putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+ putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
- build()
- }
- metadataBuilder = MediaMetadata.Builder().apply {
- putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
- putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
- }
whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
whenever(controller.transportControls).thenReturn(transportControls)
whenever(controller.playbackInfo).thenReturn(playbackInfo)
- whenever(playbackInfo.playbackType).thenReturn(
- MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL)
+ whenever(playbackInfo.playbackType)
+ .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL)
// This is an ugly hack for now. The mediaSessionBasedFilter is one of the internal
// listeners in the internal processing pipeline. It receives events, but ince it is a
@@ -170,18 +203,18 @@ class MediaDataManagerTest : SysuiTestCase() {
// treat mediaSessionBasedFilter as a listener for testing.
listener = mediaSessionBasedFilter
- val recommendationExtras = Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", DISMISS_INTENT)
- }
+ val recommendationExtras =
+ Bundle().apply {
+ putString("package_name", PACKAGE_NAME)
+ putParcelable("dismiss_intent", DISMISS_INTENT)
+ }
val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
whenever(mediaRecommendationItem.extras).thenReturn(recommendationExtras)
whenever(mediaRecommendationItem.icon).thenReturn(icon)
- validRecommendationList = listOf(
- mediaRecommendationItem, mediaRecommendationItem, mediaRecommendationItem
- )
+ validRecommendationList =
+ listOf(mediaRecommendationItem, mediaRecommendationItem, mediaRecommendationItem)
whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE)
whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
@@ -194,8 +227,11 @@ class MediaDataManagerTest : SysuiTestCase() {
fun tearDown() {
session.release()
mediaDataManager.destroy()
- Settings.Secure.putInt(context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, originalSmartspaceSetting)
+ Settings.Secure.putInt(
+ context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+ originalSmartspaceSetting
+ )
}
@Test
@@ -212,21 +248,36 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testSetTimedOut_resume_dismissesMedia() {
// WHEN resume controls are present, and time out
- val desc = MediaDescription.Builder().run {
- setTitle(SESSION_TITLE)
- build()
- }
- mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
- APP_NAME, pendingIntent, PACKAGE_NAME)
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
+ )
backgroundExecutor.runAllReady()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor),
- eq(true), eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
mediaDataManager.setTimedOut(PACKAGE_NAME, timedOut = true)
- verify(logger).logMediaTimeout(anyInt(), eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId))
+ verify(logger)
+ .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId))
// THEN it is removed and listeners are informed
foregroundExecutor.advanceClockToLast()
@@ -243,8 +294,13 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testOnMetaDataLoaded_callsListener() {
addNotificationAndLoad()
- verify(logger).logActiveMediaAdded(anyInt(), eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_LOCAL))
+ verify(logger)
+ .logActiveMediaAdded(
+ anyInt(),
+ eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId),
+ eq(MediaData.PLAYBACK_LOCAL)
+ )
}
@Test
@@ -255,56 +311,85 @@ class MediaDataManagerTest : SysuiTestCase() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
assertThat(mediaDataCaptor.value!!.active).isTrue()
}
@Test
fun testOnNotificationAdded_isRcn_markedRemote() {
- val rcn = SbnBuilder().run {
- setPkg(SYSTEM_PACKAGE_NAME)
- modifyNotification(context).also {
- it.setSmallIcon(android.R.drawable.ic_media_pause)
- it.setStyle(MediaStyle().apply {
- setMediaSession(session.sessionToken)
- setRemotePlaybackInfo("Remote device", 0, null)
- })
+ val rcn =
+ SbnBuilder().run {
+ setPkg(SYSTEM_PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(
+ MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ setRemotePlaybackInfo("Remote device", 0, null)
+ }
+ )
+ }
+ build()
}
- build()
- }
mediaDataManager.onNotificationAdded(KEY, rcn)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
- assertThat(mediaDataCaptor.value!!.playbackLocation).isEqualTo(
- MediaData.PLAYBACK_CAST_REMOTE)
- verify(logger).logActiveMediaAdded(anyInt(), eq(SYSTEM_PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value!!.playbackLocation)
+ .isEqualTo(MediaData.PLAYBACK_CAST_REMOTE)
+ verify(logger)
+ .logActiveMediaAdded(
+ anyInt(),
+ eq(SYSTEM_PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId),
+ eq(MediaData.PLAYBACK_CAST_REMOTE)
+ )
}
@Test
fun testOnNotificationAdded_hasSubstituteName_isUsed() {
val subName = "Substitute Name"
- val notif = SbnBuilder().run {
- modifyNotification(context).also {
- it.extras = Bundle().apply {
- putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName)
+ val notif =
+ SbnBuilder().run {
+ modifyNotification(context).also {
+ it.extras =
+ Bundle().apply {
+ putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName)
+ }
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
}
- it.setStyle(MediaStyle().apply {
- setMediaSession(session.sessionToken)
- })
+ build()
}
- build()
- }
mediaDataManager.onNotificationAdded(KEY, notif)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
assertThat(mediaDataCaptor.value!!.app).isEqualTo(subName)
}
@@ -314,17 +399,18 @@ class MediaDataManagerTest : SysuiTestCase() {
val bundle = Bundle()
// wrong data type
bundle.putParcelable(Notification.EXTRA_MEDIA_SESSION, Bundle())
- val rcn = SbnBuilder().run {
- setPkg(SYSTEM_PACKAGE_NAME)
- modifyNotification(context).also {
- it.setSmallIcon(android.R.drawable.ic_media_pause)
- it.addExtras(bundle)
- it.setStyle(MediaStyle().apply {
- setRemotePlaybackInfo("Remote device", 0, null)
- })
+ val rcn =
+ SbnBuilder().run {
+ setPkg(SYSTEM_PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.addExtras(bundle)
+ it.setStyle(
+ MediaStyle().apply { setRemotePlaybackInfo("Remote device", 0, null) }
+ )
+ }
+ build()
}
- build()
- }
mediaDataManager.loadMediaDataInBg(KEY, rcn, null)
// no crash even though the data structure is incorrect
@@ -335,18 +421,21 @@ class MediaDataManagerTest : SysuiTestCase() {
val bundle = Bundle()
// wrong data type
bundle.putParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, Bundle())
- val rcn = SbnBuilder().run {
- setPkg(SYSTEM_PACKAGE_NAME)
- modifyNotification(context).also {
- it.setSmallIcon(android.R.drawable.ic_media_pause)
- it.addExtras(bundle)
- it.setStyle(MediaStyle().apply {
- setMediaSession(session.sessionToken)
- setRemotePlaybackInfo("Remote device", 0, null)
- })
+ val rcn =
+ SbnBuilder().run {
+ setPkg(SYSTEM_PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.addExtras(bundle)
+ it.setStyle(
+ MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ setRemotePlaybackInfo("Remote device", 0, null)
+ }
+ )
+ }
+ build()
}
- build()
- }
mediaDataManager.loadMediaDataInBg(KEY, rcn, null)
// no crash even though the data structure is incorrect
@@ -373,8 +462,14 @@ class MediaDataManagerTest : SysuiTestCase() {
mediaDataManager.onNotificationRemoved(KEY)
// THEN the media data indicates that it is for resumption
verify(listener)
- .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
verify(logger).logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
@@ -389,8 +484,14 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
verify(listener)
- .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
val data = mediaDataCaptor.value
assertThat(data.resumption).isFalse()
val resumableData = data.copy(resumeAction = Runnable {})
@@ -401,8 +502,14 @@ class MediaDataManagerTest : SysuiTestCase() {
mediaDataManager.onNotificationRemoved(KEY)
// THEN the data is for resumption and the key is migrated to the package name
verify(listener)
- .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
assertThat(mediaDataCaptor.value.resumption).isTrue()
verify(listener, never()).onMediaDataRemoved(eq(KEY))
// WHEN the second is removed
@@ -410,8 +517,13 @@ class MediaDataManagerTest : SysuiTestCase() {
// THEN the data is for resumption and the second key is removed
verify(listener)
.onMediaDataLoaded(
- eq(PACKAGE_NAME), eq(PACKAGE_NAME), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
+ eq(PACKAGE_NAME),
+ eq(PACKAGE_NAME),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
assertThat(mediaDataCaptor.value.resumption).isTrue()
verify(listener).onMediaDataRemoved(eq(KEY_2))
}
@@ -420,15 +532,20 @@ class MediaDataManagerTest : SysuiTestCase() {
fun testOnNotificationRemoved_withResumption_butNotLocal() {
// GIVEN that the manager has a notification with a resume action, but is not local
whenever(controller.metadata).thenReturn(metadataBuilder.build())
- whenever(playbackInfo.playbackType).thenReturn(
- MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+ whenever(playbackInfo.playbackType)
+ .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
addNotificationAndLoad()
val data = mediaDataCaptor.value
- val dataRemoteWithResume = data.copy(resumeAction = Runnable {},
- playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
+ val dataRemoteWithResume =
+ data.copy(resumeAction = Runnable {}, playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume)
- verify(logger).logActiveMediaAdded(anyInt(), eq(PACKAGE_NAME),
- eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_CAST_LOCAL))
+ verify(logger)
+ .logActiveMediaAdded(
+ anyInt(),
+ eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId),
+ eq(MediaData.PLAYBACK_CAST_LOCAL)
+ )
// WHEN the notification is removed
mediaDataManager.onNotificationRemoved(KEY)
@@ -440,19 +557,33 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testAddResumptionControls() {
// WHEN resumption controls are added
- val desc = MediaDescription.Builder().run {
- setTitle(SESSION_TITLE)
- build()
- }
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
val currentTime = clock.elapsedRealtime()
- mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
- APP_NAME, pendingIntent, PACKAGE_NAME)
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
+ )
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
// THEN the media data indicates that it is for resumption
verify(listener)
- .onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
val data = mediaDataCaptor.value
assertThat(data.resumption).isTrue()
assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -466,16 +597,31 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testResumptionDisabled_dismissesResumeControls() {
// WHEN there are resume controls and resumption is switched off
- val desc = MediaDescription.Builder().run {
- setTitle(SESSION_TITLE)
- build()
- }
- mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
- APP_NAME, pendingIntent, PACKAGE_NAME)
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
+ )
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor),
- eq(true), eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
val data = mediaDataCaptor.value
mediaDataManager.setMediaResumptionEnabled(false)
@@ -508,23 +654,30 @@ class MediaDataManagerTest : SysuiTestCase() {
fun testBadArtwork_doesNotUse() {
// WHEN notification has a too-small artwork
val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
- val notif = SbnBuilder().run {
- setPkg(PACKAGE_NAME)
- modifyNotification(context).also {
- it.setSmallIcon(android.R.drawable.ic_media_pause)
- it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
- it.setLargeIcon(artwork)
+ val notif =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ it.setLargeIcon(artwork)
+ }
+ build()
}
- build()
- }
mediaDataManager.onNotificationAdded(KEY, notif)
// THEN it still loads
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
verify(listener)
- .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
}
@Test
@@ -533,18 +686,23 @@ class MediaDataManagerTest : SysuiTestCase() {
verify(logger).getNewInstanceId()
val instanceId = instanceIdSequence.lastInstanceId
- verify(listener).onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = 1234L,
- instanceId = InstanceId.fakeInstanceId(instanceId))),
- eq(false))
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(
+ eq(KEY_MEDIA_SMARTSPACE),
+ eq(
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ packageName = PACKAGE_NAME,
+ cardAction = mediaSmartspaceBaseAction,
+ recommendations = validRecommendationList,
+ dismissIntent = DISMISS_INTENT,
+ headphoneConnectionTimeMillis = 1234L,
+ instanceId = InstanceId.fakeInstanceId(instanceId)
+ )
+ ),
+ eq(false)
+ )
}
@Test
@@ -554,23 +712,29 @@ class MediaDataManagerTest : SysuiTestCase() {
verify(logger).getNewInstanceId()
val instanceId = instanceIdSequence.lastInstanceId
- verify(listener).onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = 1234L,
- instanceId = InstanceId.fakeInstanceId(instanceId))),
- eq(false))
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(
+ eq(KEY_MEDIA_SMARTSPACE),
+ eq(
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ dismissIntent = DISMISS_INTENT,
+ headphoneConnectionTimeMillis = 1234L,
+ instanceId = InstanceId.fakeInstanceId(instanceId)
+ )
+ ),
+ eq(false)
+ )
}
@Test
fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() {
- val recommendationExtras = Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", null)
- }
+ val recommendationExtras =
+ Bundle().apply {
+ putString("package_name", PACKAGE_NAME)
+ putParcelable("dismiss_intent", null)
+ }
whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
@@ -579,15 +743,20 @@ class MediaDataManagerTest : SysuiTestCase() {
verify(logger).getNewInstanceId()
val instanceId = instanceIdSequence.lastInstanceId
- verify(listener).onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = null,
- headphoneConnectionTimeMillis = 1234L,
- instanceId = InstanceId.fakeInstanceId(instanceId))),
- eq(false))
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(
+ eq(KEY_MEDIA_SMARTSPACE),
+ eq(
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ dismissIntent = null,
+ headphoneConnectionTimeMillis = 1234L,
+ instanceId = InstanceId.fakeInstanceId(instanceId)
+ )
+ ),
+ eq(false)
+ )
}
@Test
@@ -595,7 +764,7 @@ class MediaDataManagerTest : SysuiTestCase() {
smartspaceMediaDataProvider.onTargetsAvailable(listOf())
verify(logger, never()).getNewInstanceId()
verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
+ .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
}
@Ignore("b/233283726")
@@ -615,15 +784,18 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
// WHEN media recommendation setting is off
- Settings.Secure.putInt(context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
+ Settings.Secure.putInt(
+ context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+ 0
+ )
tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
// THEN smartspace signal is ignored
verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
+ .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
}
@Ignore("b/229838140")
@@ -631,12 +803,15 @@ class MediaDataManagerTest : SysuiTestCase() {
fun testMediaRecommendationDisabled_removesSmartspaceData() {
// GIVEN a media recommendation card is present
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(listener).onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(),
- anyBoolean())
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), anyBoolean())
// WHEN the media recommendation setting is turned off
- Settings.Secure.putInt(context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
+ Settings.Secure.putInt(
+ context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+ 0
+ )
tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
// THEN listeners are notified
@@ -665,8 +840,15 @@ class MediaDataManagerTest : SysuiTestCase() {
mediaDataManager.setTimedOut(KEY, true, true)
// THEN the last active time is not changed
- verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
}
@@ -687,8 +869,14 @@ class MediaDataManagerTest : SysuiTestCase() {
// THEN the last active time is not changed
verify(listener)
- .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
@@ -700,17 +888,20 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testTooManyCompactActions_isTruncated() {
// GIVEN a notification where too many compact actions were specified
- val notif = SbnBuilder().run {
- setPkg(PACKAGE_NAME)
- modifyNotification(context).also {
- it.setSmallIcon(android.R.drawable.ic_media_pause)
- it.setStyle(MediaStyle().apply {
- setMediaSession(session.sessionToken)
- setShowActionsInCompactView(0, 1, 2, 3, 4)
- })
+ val notif =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(
+ MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ setShowActionsInCompactView(0, 1, 2, 3, 4)
+ }
+ )
+ }
+ build()
}
- build()
- }
// WHEN the notification is loaded
mediaDataManager.onNotificationAdded(KEY, notif)
@@ -718,29 +909,35 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
// THEN only the first MAX_COMPACT_ACTIONS are actually set
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
- assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo(
- MediaDataManager.MAX_COMPACT_ACTIONS)
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.actionsToShowInCompact.size)
+ .isEqualTo(MediaDataManager.MAX_COMPACT_ACTIONS)
}
@Test
fun testTooManyNotificationActions_isTruncated() {
// GIVEN a notification where too many notification actions are added
val action = Notification.Action(R.drawable.ic_android, "action", null)
- val notif = SbnBuilder().run {
- setPkg(PACKAGE_NAME)
- modifyNotification(context).also {
- it.setSmallIcon(android.R.drawable.ic_media_pause)
- it.setStyle(MediaStyle().apply {
- setMediaSession(session.sessionToken)
- })
- for (i in 0..MediaDataManager.MAX_NOTIFICATION_ACTIONS) {
- it.addAction(action)
+ val notif =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ for (i in 0..MediaDataManager.MAX_NOTIFICATION_ACTIONS) {
+ it.addAction(action)
+ }
}
+ build()
}
- build()
- }
// WHEN the notification is loaded
mediaDataManager.onNotificationAdded(KEY, notif)
@@ -748,10 +945,17 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
// THEN only the first MAX_NOTIFICATION_ACTIONS are actually included
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
- assertThat(mediaDataCaptor.value.actions.size).isEqualTo(
- MediaDataManager.MAX_NOTIFICATION_ACTIONS)
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.actions.size)
+ .isEqualTo(MediaDataManager.MAX_NOTIFICATION_ACTIONS)
}
@Test
@@ -760,21 +964,29 @@ class MediaDataManagerTest : SysuiTestCase() {
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
whenever(controller.playbackState).thenReturn(null)
- val notifWithAction = SbnBuilder().run {
- setPkg(PACKAGE_NAME)
- modifyNotification(context).also {
- it.setSmallIcon(android.R.drawable.ic_media_pause)
- it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
- it.addAction(android.R.drawable.ic_media_play, desc, null)
+ val notifWithAction =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ it.addAction(android.R.drawable.ic_media_play, desc, null)
+ }
+ build()
}
- build()
- }
mediaDataManager.onNotificationAdded(KEY, notifWithAction)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
assertThat(mediaDataCaptor.value!!.semanticActions).isNull()
assertThat(mediaDataCaptor.value!!.actions).hasSize(1)
@@ -785,11 +997,11 @@ class MediaDataManagerTest : SysuiTestCase() {
fun testPlaybackActions_hasPrevNext() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
- val stateActions = PlaybackState.ACTION_PLAY or
+ val stateActions =
+ PlaybackState.ACTION_PLAY or
PlaybackState.ACTION_SKIP_TO_PREVIOUS or
PlaybackState.ACTION_SKIP_TO_NEXT
- val stateBuilder = PlaybackState.Builder()
- .setActions(stateActions)
+ val stateBuilder = PlaybackState.Builder().setActions(stateActions)
customDesc.forEach {
stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
}
@@ -801,20 +1013,20 @@ class MediaDataManagerTest : SysuiTestCase() {
val actions = mediaDataCaptor.value!!.semanticActions!!
assertThat(actions.playOrPause).isNotNull()
- assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
- context.getString(R.string.controls_media_button_play))
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_play))
actions.playOrPause!!.action!!.run()
verify(transportControls).play()
assertThat(actions.prevOrCustom).isNotNull()
- assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(
- context.getString(R.string.controls_media_button_prev))
+ assertThat(actions.prevOrCustom!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_prev))
actions.prevOrCustom!!.action!!.run()
verify(transportControls).skipToPrevious()
assertThat(actions.nextOrCustom).isNotNull()
- assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(
- context.getString(R.string.controls_media_button_next))
+ assertThat(actions.nextOrCustom!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_next))
actions.nextOrCustom!!.action!!.run()
verify(transportControls).skipToNext()
@@ -830,8 +1042,7 @@ class MediaDataManagerTest : SysuiTestCase() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
val stateActions = PlaybackState.ACTION_PLAY
- val stateBuilder = PlaybackState.Builder()
- .setActions(stateActions)
+ val stateBuilder = PlaybackState.Builder().setActions(stateActions)
customDesc.forEach {
stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
}
@@ -843,8 +1054,8 @@ class MediaDataManagerTest : SysuiTestCase() {
val actions = mediaDataCaptor.value!!.semanticActions!!
assertThat(actions.playOrPause).isNotNull()
- assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
- context.getString(R.string.controls_media_button_play))
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_play))
assertThat(actions.prevOrCustom).isNotNull()
assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(customDesc[0])
@@ -863,7 +1074,8 @@ class MediaDataManagerTest : SysuiTestCase() {
fun testPlaybackActions_connecting() {
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
val stateActions = PlaybackState.ACTION_PLAY
- val stateBuilder = PlaybackState.Builder()
+ val stateBuilder =
+ PlaybackState.Builder()
.setState(PlaybackState.STATE_BUFFERING, 0, 10f)
.setActions(stateActions)
whenever(controller.playbackState).thenReturn(stateBuilder.build())
@@ -874,8 +1086,8 @@ class MediaDataManagerTest : SysuiTestCase() {
val actions = mediaDataCaptor.value!!.semanticActions!!
assertThat(actions.playOrPause).isNotNull()
- assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
- context.getString(R.string.controls_media_button_connecting))
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_connecting))
}
@Test
@@ -883,15 +1095,15 @@ class MediaDataManagerTest : SysuiTestCase() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
val stateActions = PlaybackState.ACTION_PLAY
- val stateBuilder = PlaybackState.Builder()
- .setActions(stateActions)
+ val stateBuilder = PlaybackState.Builder().setActions(stateActions)
customDesc.forEach {
stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
}
- val extras = Bundle().apply {
- putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
- putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
- }
+ val extras =
+ Bundle().apply {
+ putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
+ putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
+ }
whenever(controller.playbackState).thenReturn(stateBuilder.build())
whenever(controller.extras).thenReturn(extras)
@@ -901,8 +1113,8 @@ class MediaDataManagerTest : SysuiTestCase() {
val actions = mediaDataCaptor.value!!.semanticActions!!
assertThat(actions.playOrPause).isNotNull()
- assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
- context.getString(R.string.controls_media_button_play))
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_play))
assertThat(actions.prevOrCustom).isNull()
assertThat(actions.nextOrCustom).isNull()
@@ -930,8 +1142,8 @@ class MediaDataManagerTest : SysuiTestCase() {
val actions = mediaDataCaptor.value!!.semanticActions!!
assertThat(actions.playOrPause).isNotNull()
- assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
- context.getString(R.string.controls_media_button_play))
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_play))
actions.playOrPause!!.action!!.run()
verify(transportControls).play()
}
@@ -944,30 +1156,43 @@ class MediaDataManagerTest : SysuiTestCase() {
// Location is updated to local cast
whenever(controller.metadata).thenReturn(metadataBuilder.build())
- whenever(playbackInfo.playbackType).thenReturn(
- MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+ whenever(playbackInfo.playbackType)
+ .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
addNotificationAndLoad()
- verify(logger).logPlaybackLocationChange(anyInt(), eq(PACKAGE_NAME),
- eq(instanceId), eq(MediaData.PLAYBACK_CAST_LOCAL))
+ verify(logger)
+ .logPlaybackLocationChange(
+ anyInt(),
+ eq(PACKAGE_NAME),
+ eq(instanceId),
+ eq(MediaData.PLAYBACK_CAST_LOCAL)
+ )
// update to remote cast
- val rcn = SbnBuilder().run {
- setPkg(SYSTEM_PACKAGE_NAME) // System package
- modifyNotification(context).also {
- it.setSmallIcon(android.R.drawable.ic_media_pause)
- it.setStyle(MediaStyle().apply {
- setMediaSession(session.sessionToken)
- setRemotePlaybackInfo("Remote device", 0, null)
- })
+ val rcn =
+ SbnBuilder().run {
+ setPkg(SYSTEM_PACKAGE_NAME) // System package
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(
+ MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ setRemotePlaybackInfo("Remote device", 0, null)
+ }
+ )
+ }
+ build()
}
- build()
- }
mediaDataManager.onNotificationAdded(KEY, rcn)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(logger).logPlaybackLocationChange(anyInt(), eq(SYSTEM_PACKAGE_NAME),
- eq(instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE))
+ verify(logger)
+ .logPlaybackLocationChange(
+ anyInt(),
+ eq(SYSTEM_PACKAGE_NAME),
+ eq(instanceId),
+ eq(MediaData.PLAYBACK_CAST_REMOTE)
+ )
}
@Test
@@ -977,14 +1202,19 @@ class MediaDataManagerTest : SysuiTestCase() {
verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
// Callback gets an updated state
- val state = PlaybackState.Builder()
- .setState(PlaybackState.STATE_PLAYING, 0L, 1f)
- .build()
+ val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
callbackCaptor.value.invoke(KEY, state)
// Listener is notified of updated state
- verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY),
- capture(mediaDataCaptor), eq(true), eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
assertThat(mediaDataCaptor.value.isPlaying).isTrue()
}
@@ -996,8 +1226,8 @@ class MediaDataManagerTest : SysuiTestCase() {
// No media added with this key
callbackCaptor.value.invoke(KEY, state)
- verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(),
- anyBoolean())
+ verify(listener, never())
+ .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@Test
@@ -1015,35 +1245,42 @@ class MediaDataManagerTest : SysuiTestCase() {
// Then no changes are made
callbackCaptor.value.invoke(KEY, state)
- verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(),
- anyBoolean())
+ verify(listener, never())
+ .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@Test
fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() {
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
- val state = PlaybackState.Builder()
- .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
- .build()
+ val state = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 1f).build()
whenever(controller.playbackState).thenReturn(state)
addNotificationAndLoad()
verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
callbackCaptor.value.invoke(KEY, state)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY),
- capture(mediaDataCaptor), eq(true), eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
}
@Test
fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() {
- val desc = MediaDescription.Builder().run {
- setTitle(SESSION_TITLE)
- build()
- }
- val state = PlaybackState.Builder()
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ val state =
+ PlaybackState.Builder()
.setState(PlaybackState.STATE_PAUSED, 0L, 1f)
.setActions(PlaybackState.ACTION_PLAY_PAUSE)
.build()
@@ -1051,13 +1288,13 @@ class MediaDataManagerTest : SysuiTestCase() {
// Add resumption controls in order to have semantic actions.
// To make sure that they are not null after changing state.
mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
)
backgroundExecutor.runAllReady()
foregroundExecutor.runAllReady()
@@ -1066,14 +1303,14 @@ class MediaDataManagerTest : SysuiTestCase() {
callbackCaptor.value.invoke(PACKAGE_NAME, state)
verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(PACKAGE_NAME),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
- )
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(PACKAGE_NAME),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
}
@@ -1081,7 +1318,8 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testPlaybackStateNull_Pause_keyExists_callsListener() {
whenever(controller.playbackState).thenReturn(null)
- val state = PlaybackState.Builder()
+ val state =
+ PlaybackState.Builder()
.setState(PlaybackState.STATE_PAUSED, 0L, 1f)
.setActions(PlaybackState.ACTION_PLAY_PAUSE)
.build()
@@ -1090,20 +1328,32 @@ class MediaDataManagerTest : SysuiTestCase() {
verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
callbackCaptor.value.invoke(KEY, state)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY),
- capture(mediaDataCaptor), eq(true), eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
assertThat(mediaDataCaptor.value.semanticActions).isNull()
}
- /**
- * Helper function to add a media notification and capture the resulting MediaData
- */
+ /** Helper function to add a media notification and capture the resulting MediaData */
private fun addNotificationAndLoad() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0), eq(false))
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
index 121c8946d164..a45e9d9fcacf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
import android.bluetooth.BluetoothLeBroadcast
import android.bluetooth.BluetoothLeBroadcastMetadata
@@ -37,6 +37,10 @@ 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.controls.MediaTestUtils
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.player.MediaDeviceData
+import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -109,7 +113,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
fakeFgExecutor = FakeExecutor(FakeSystemClock())
fakeBgExecutor = FakeExecutor(FakeSystemClock())
localBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager::class.java)
- manager = MediaDeviceManager(
+ manager =
+ MediaDeviceManager(
context,
controllerFactory,
lmmFactory,
@@ -120,7 +125,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
fakeFgExecutor,
fakeBgExecutor,
dumpster
- )
+ )
manager.addListener(listener)
// Configure mocks.
@@ -134,11 +139,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
// Create a media sesssion and notification for testing.
session = MediaSession(context, SESSION_KEY)
- mediaData = MediaTestUtils.emptyMediaData.copy(
- packageName = PACKAGE,
- token = session.sessionToken)
- whenever(controllerFactory.create(session.sessionToken))
- .thenReturn(controller)
+ mediaData =
+ MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken)
+ whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller)
setupLeAudioConfiguration(false)
}
@@ -354,7 +357,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
val deviceCallback = captureCallback()
// First set a non-null about-to-connect device
deviceCallback.onAboutToConnectDeviceAdded(
- "fakeAddress", "AboutToConnectDeviceName", mock(Drawable::class.java)
+ "fakeAddress",
+ "AboutToConnectDeviceName",
+ mock(Drawable::class.java)
)
// Run and reset the executors and listeners so we only focus on new events.
fakeBgExecutor.runAllReady()
@@ -583,8 +588,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
@Test
fun testRemotePlaybackDeviceOverride() {
whenever(route.name).thenReturn(DEVICE_NAME)
- val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null,
- showBroadcastButton = false)
+ 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
@@ -613,8 +618,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
val data = captureDeviceData(KEY)
assertThat(data.showBroadcastButton).isTrue()
assertThat(data.enabled).isTrue()
- assertThat(data.name).isEqualTo(context.getString(
- R.string.broadcasting_description_is_broadcasting))
+ assertThat(data.name)
+ .isEqualTo(context.getString(R.string.broadcasting_description_is_broadcasting))
}
@Test
@@ -655,20 +660,21 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
}
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
- ) {}
- }
+ 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
@@ -677,7 +683,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
fun setupLeAudioConfiguration(isLeAudio: Boolean) {
whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager)
whenever(localBluetoothProfileManager.leAudioBroadcastProfile)
- .thenReturn(localBluetoothLeBroadcast)
+ .thenReturn(localBluetoothLeBroadcast)
whenever(localBluetoothLeBroadcast.isEnabled(any())).thenReturn(isLeAudio)
whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME)
}
@@ -685,7 +691,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
fun setupBroadcastPackage(currentName: String) {
whenever(lmm.packageName).thenReturn(PACKAGE)
whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt()))
- .thenReturn(applicationInfo)
+ .thenReturn(applicationInfo)
whenever(packageManager.getApplicationLabel(applicationInfo)).thenReturn(currentName)
context.setMockPackageManager(packageManager)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt
index 558645377936..3099609d42f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
import android.media.session.MediaController
import android.media.session.MediaController.PlaybackInfo
@@ -23,12 +23,12 @@ import android.media.session.MediaSessionManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
-
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.MediaTestUtils
+import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
-
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -42,17 +42,15 @@ import org.mockito.Mockito.any
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
private const val PACKAGE = "PKG"
private const val KEY = "TEST_KEY"
private const val NOTIF_KEY = "TEST_KEY"
-private val info = MediaTestUtils.emptyMediaData.copy(
- packageName = PACKAGE,
- notificationKey = NOTIF_KEY
-)
+private val info =
+ MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, notificationKey = NOTIF_KEY)
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -139,10 +137,10 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
// Capture listener
bgExecutor.runAllReady()
- val listenerCaptor = ArgumentCaptor.forClass(
- MediaSessionManager.OnActiveSessionsChangedListener::class.java)
- verify(mediaSessionManager).addOnActiveSessionsChangedListener(
- listenerCaptor.capture(), any())
+ val listenerCaptor =
+ ArgumentCaptor.forClass(MediaSessionManager.OnActiveSessionsChangedListener::class.java)
+ verify(mediaSessionManager)
+ .addOnActiveSessionsChangedListener(listenerCaptor.capture(), any())
sessionListener = listenerCaptor.value
filter.addListener(mediaListener)
@@ -161,8 +159,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
filter.onMediaDataLoaded(KEY, null, mediaData1)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
- verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
}
@Test
@@ -184,8 +182,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
- verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
}
@Test
@@ -214,8 +212,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
- verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
}
@Test
@@ -230,15 +228,22 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
- verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
// WHEN a loaded event is received that matches the local session
filter.onMediaDataLoaded(KEY, null, mediaData2)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is filtered
- verify(mediaListener, never()).onMediaDataLoaded(
- eq(KEY), eq(null), eq(mediaData2), anyBoolean(), anyInt(), anyBoolean())
+ verify(mediaListener, never())
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ eq(mediaData2),
+ anyBoolean(),
+ anyInt(),
+ anyBoolean()
+ )
}
@Test
@@ -254,8 +259,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
fgExecutor.runAllReady()
// THEN the event is not filtered because there isn't a notification for the remote
// session.
- verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
}
@Test
@@ -272,16 +277,22 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
- verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
// WHEN a loaded event is received that matches the local session
filter.onMediaDataLoaded(key2, null, mediaData2)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is filtered
verify(mediaListener, never())
- .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(),
- anyInt(), anyBoolean())
+ .onMediaDataLoaded(
+ eq(key2),
+ eq(null),
+ eq(mediaData2),
+ anyBoolean(),
+ anyInt(),
+ anyBoolean()
+ )
// AND there should be a removed event for key2
verify(mediaListener).onMediaDataRemoved(eq(key2))
}
@@ -300,15 +311,15 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
- verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
// WHEN a loaded event is received that matches the remote session
filter.onMediaDataLoaded(key2, null, mediaData2)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
- verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), eq(true), eq(0), eq(false))
}
@Test
@@ -324,15 +335,15 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
- verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
// WHEN a loaded event is received that matches the local session
filter.onMediaDataLoaded(KEY, null, mediaData2)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
- verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2), eq(true), eq(0), eq(false))
}
@Test
@@ -350,8 +361,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
- verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
}
@Test
@@ -373,8 +384,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the key migration event is fired
- verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true), eq(0), eq(false))
}
@Test
@@ -404,14 +415,20 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
fgExecutor.runAllReady()
// THEN the key migration event is filtered
verify(mediaListener, never())
- .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(),
- anyInt(), anyBoolean())
+ .onMediaDataLoaded(
+ eq(key2),
+ eq(null),
+ eq(mediaData2),
+ anyBoolean(),
+ anyInt(),
+ anyBoolean()
+ )
// WHEN a loaded event is received that matches the remote session
filter.onMediaDataLoaded(key2, null, mediaData1)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the key migration event is fired
- verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1), eq(true),
- eq(0), eq(false))
+ verify(mediaListener)
+ .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
index 823d4ae8c447..344dffafb448 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.pipeline
import android.media.MediaMetadata
import android.media.session.MediaController
@@ -23,6 +23,9 @@ import android.media.session.PlaybackState
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.MediaTestUtils
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.concurrency.FakeExecutor
@@ -41,11 +44,11 @@ import org.mockito.ArgumentMatchers.anyString
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
private const val KEY = "KEY"
@@ -70,7 +73,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
@Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit
@Mock private lateinit var stateCallback: (String, PlaybackState) -> Unit
@Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback>
- @Captor private lateinit var dozingCallbackCaptor:
+ @Captor
+ private lateinit var dozingCallbackCaptor:
ArgumentCaptor<StatusBarStateController.StateListener>
@JvmField @Rule val mockito = MockitoJUnit.rule()
private lateinit var metadataBuilder: MediaMetadata.Builder
@@ -85,36 +89,41 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
fun setup() {
`when`(mediaControllerFactory.create(any())).thenReturn(mediaController)
executor = FakeExecutor(clock)
- mediaTimeoutListener = MediaTimeoutListener(
- mediaControllerFactory,
- executor,
- logger,
- statusBarStateController,
- clock
- )
+ mediaTimeoutListener =
+ MediaTimeoutListener(
+ mediaControllerFactory,
+ executor,
+ logger,
+ statusBarStateController,
+ clock
+ )
mediaTimeoutListener.timeoutCallback = timeoutCallback
mediaTimeoutListener.stateCallback = stateCallback
// Create a media session and notification for testing.
- metadataBuilder = MediaMetadata.Builder().apply {
- putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
- putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
- }
- 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())
- }
+ metadataBuilder =
+ MediaMetadata.Builder().apply {
+ putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+ putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+ }
+ 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(
- app = PACKAGE,
- packageName = PACKAGE,
- token = session.sessionToken
- )
+ mediaData =
+ MediaTestUtils.emptyMediaData.copy(
+ app = PACKAGE,
+ packageName = PACKAGE,
+ token = session.sessionToken
+ )
resumeData = mediaData.copy(token = null, active = false, resumption = true)
}
@@ -212,8 +221,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
// Assuming we're registered
testOnMediaDataLoaded_registersPlaybackListener()
- mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
- .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
+ mediaCallbackCaptor.value.onPlaybackStateChanged(
+ PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
+ )
assertThat(executor.numPending()).isEqualTo(1)
assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
}
@@ -223,8 +233,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
// Assuming we have a pending timeout
testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
- mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
- .setState(PlaybackState.STATE_PLAYING, 0L, 0f).build())
+ mediaCallbackCaptor.value.onPlaybackStateChanged(
+ PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 0f).build()
+ )
assertThat(executor.numPending()).isEqualTo(0)
verify(logger).logTimeoutCancelled(eq(KEY), any())
}
@@ -234,8 +245,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
// Assuming we have a pending timeout
testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
- mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
- .setState(PlaybackState.STATE_STOPPED, 0L, 0f).build())
+ mediaCallbackCaptor.value.onPlaybackStateChanged(
+ PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0L, 0f).build()
+ )
assertThat(executor.numPending()).isEqualTo(1)
}
@@ -329,9 +341,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
@Test
fun testOnMediaDataLoaded_pausedToResume_updatesTimeout() {
// WHEN regular media is paused
- val pausedState = PlaybackState.Builder()
- .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
- .build()
+ val pausedState =
+ PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
`when`(mediaController.playbackState).thenReturn(pausedState)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
assertThat(executor.numPending()).isEqualTo(1)
@@ -362,9 +373,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData)
// AND that media is resumed
- val playingState = PlaybackState.Builder()
- .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
- .build()
+ val playingState =
+ PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
`when`(mediaController.playbackState).thenReturn(playingState)
mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData)
@@ -386,15 +396,11 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
@Test
fun testOnMediaDataLoaded_playbackActionsChanged_noCallback() {
// Load media data once
- val pausedState = PlaybackState.Builder()
- .setActions(PlaybackState.ACTION_PAUSE)
- .build()
+ val pausedState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PAUSE).build()
loadMediaDataWithPlaybackState(pausedState)
// When media data is loaded again, with different actions
- val playingState = PlaybackState.Builder()
- .setActions(PlaybackState.ACTION_PLAY)
- .build()
+ val playingState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build()
loadMediaDataWithPlaybackState(playingState)
// Then the callback is not invoked
@@ -404,15 +410,11 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
@Test
fun testOnPlaybackStateChanged_playbackActionsChanged_sendsCallback() {
// Load media data once
- val pausedState = PlaybackState.Builder()
- .setActions(PlaybackState.ACTION_PAUSE)
- .build()
+ val pausedState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PAUSE).build()
loadMediaDataWithPlaybackState(pausedState)
// When the playback state changes, and has different actions
- val playingState = PlaybackState.Builder()
- .setActions(PlaybackState.ACTION_PLAY)
- .build()
+ val playingState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build()
mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
// Then the callback is invoked
@@ -421,24 +423,30 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
@Test
fun testOnPlaybackStateChanged_differentCustomActions_sendsCallback() {
- val customOne = PlaybackState.CustomAction.Builder(
+ val customOne =
+ PlaybackState.CustomAction.Builder(
"ACTION_1",
"custom action 1",
- android.R.drawable.ic_media_ff)
+ android.R.drawable.ic_media_ff
+ )
.build()
- val pausedState = PlaybackState.Builder()
+ val pausedState =
+ PlaybackState.Builder()
.setActions(PlaybackState.ACTION_PAUSE)
.addCustomAction(customOne)
.build()
loadMediaDataWithPlaybackState(pausedState)
// When the playback state actions change
- val customTwo = PlaybackState.CustomAction.Builder(
- "ACTION_2",
- "custom action 2",
- android.R.drawable.ic_media_rew)
+ val customTwo =
+ PlaybackState.CustomAction.Builder(
+ "ACTION_2",
+ "custom action 2",
+ android.R.drawable.ic_media_rew
+ )
.build()
- val pausedStateTwoActions = PlaybackState.Builder()
+ val pausedStateTwoActions =
+ PlaybackState.Builder()
.setActions(PlaybackState.ACTION_PAUSE)
.addCustomAction(customOne)
.addCustomAction(customTwo)
@@ -451,9 +459,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
@Test
fun testOnPlaybackStateChanged_sameActions_noCallback() {
- val stateWithActions = PlaybackState.Builder()
- .setActions(PlaybackState.ACTION_PLAY)
- .build()
+ val stateWithActions = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build()
loadMediaDataWithPlaybackState(stateWithActions)
// When the playback state updates with the same actions
@@ -467,18 +473,20 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
fun testOnPlaybackStateChanged_sameCustomActions_noCallback() {
val actionName = "custom action"
val actionIcon = android.R.drawable.ic_media_ff
- val customOne = PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon)
- .build()
- val stateOne = PlaybackState.Builder()
+ val customOne =
+ PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon).build()
+ val stateOne =
+ PlaybackState.Builder()
.setActions(PlaybackState.ACTION_PAUSE)
.addCustomAction(customOne)
.build()
loadMediaDataWithPlaybackState(stateOne)
// When the playback state is updated, but has the same actions
- val customTwo = PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon)
- .build()
- val stateTwo = PlaybackState.Builder()
+ val customTwo =
+ PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon).build()
+ val stateTwo =
+ PlaybackState.Builder()
.setActions(PlaybackState.ACTION_PAUSE)
.addCustomAction(customTwo)
.build()
@@ -491,15 +499,13 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
@Test
fun testOnMediaDataLoaded_isPlayingChanged_noCallback() {
// Load media data in paused state
- val pausedState = PlaybackState.Builder()
- .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
- .build()
+ val pausedState =
+ PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
loadMediaDataWithPlaybackState(pausedState)
// When media data is loaded again but playing
- val playingState = PlaybackState.Builder()
- .setState(PlaybackState.STATE_PLAYING, 0L, 1f)
- .build()
+ val playingState =
+ PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
loadMediaDataWithPlaybackState(playingState)
// Then the callback is not invoked
@@ -509,15 +515,13 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
@Test
fun testOnPlaybackStateChanged_isPlayingChanged_sendsCallback() {
// Load media data in paused state
- val pausedState = PlaybackState.Builder()
- .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
- .build()
+ val pausedState =
+ PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
loadMediaDataWithPlaybackState(pausedState)
// When the playback state changes to playing
- val playingState = PlaybackState.Builder()
- .setState(PlaybackState.STATE_PLAYING, 0L, 1f)
- .build()
+ val playingState =
+ PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
// Then the callback is invoked
@@ -527,15 +531,13 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
@Test
fun testOnPlaybackStateChanged_isPlayingSame_noCallback() {
// Load media data in paused state
- val pausedState = PlaybackState.Builder()
- .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
- .build()
+ val pausedState =
+ PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
loadMediaDataWithPlaybackState(pausedState)
// When the playback state is updated, but still not playing
- val playingState = PlaybackState.Builder()
- .setState(PlaybackState.STATE_STOPPED, 0L, 0f)
- .build()
+ val playingState =
+ PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0L, 0f).build()
mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
// Then the callback is not invoked
@@ -546,8 +548,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
fun testTimeoutCallback_dozedPastTimeout_invokedOnWakeup() {
// When paused media is loaded
testOnMediaDataLoaded_registersPlaybackListener()
- mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
- .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
+ mediaCallbackCaptor.value.onPlaybackStateChanged(
+ PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
+ )
verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
// And we doze past the scheduled timeout
@@ -571,8 +574,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
val time = clock.currentTimeMillis()
clock.setElapsedRealtime(time)
testOnMediaDataLoaded_registersPlaybackListener()
- mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
- .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
+ mediaCallbackCaptor.value.onPlaybackStateChanged(
+ PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
+ )
verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
// And we doze, but not past the scheduled timeout
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
index 3d3ac836d264..84fdfd78e9fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.resume
import android.app.PendingIntent
import android.content.ComponentName
@@ -33,11 +33,16 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.controls.MediaTestUtils
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.player.MediaDeviceData
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
-import org.junit.After
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,7 +68,9 @@ private const val MEDIA_PREFERENCES = "media_control_prefs"
private const val RESUME_COMPONENTS = "package1/class1:package2/class2:package3/class3"
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
private fun <T> any(): T = Mockito.any<T>()
@SmallTest
@@ -93,26 +100,32 @@ class MediaResumeListenerTest : SysuiTestCase() {
private lateinit var resumeListener: MediaResumeListener
private val clock = FakeSystemClock()
- private var originalQsSetting = Settings.Global.getInt(context.contentResolver,
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
- private var originalResumeSetting = Settings.Secure.getInt(context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+ private var originalQsSetting =
+ Settings.Global.getInt(
+ context.contentResolver,
+ Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS,
+ 1
+ )
+ private var originalResumeSetting =
+ Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- Settings.Global.putInt(context.contentResolver,
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
- Settings.Secure.putInt(context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
+ Settings.Global.putInt(
+ context.contentResolver,
+ Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS,
+ 1
+ )
+ Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
- .thenReturn(resumeBrowser)
+ .thenReturn(resumeBrowser)
// resume components are stored in sharedpreferences
whenever(mockContext.getSharedPreferences(eq(MEDIA_PREFERENCES), anyInt()))
- .thenReturn(sharedPrefs)
+ .thenReturn(sharedPrefs)
whenever(sharedPrefs.getString(any(), any())).thenReturn(RESUME_COMPONENTS)
whenever(sharedPrefs.edit()).thenReturn(sharedPrefsEditor)
whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor)
@@ -120,36 +133,59 @@ class MediaResumeListenerTest : SysuiTestCase() {
whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
executor = FakeExecutor(clock)
- resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
- tunerService, resumeBrowserFactory, dumpManager, clock)
+ resumeListener =
+ MediaResumeListener(
+ mockContext,
+ broadcastDispatcher,
+ executor,
+ tunerService,
+ resumeBrowserFactory,
+ dumpManager,
+ clock
+ )
resumeListener.setManager(mediaDataManager)
mediaDataManager.addListener(resumeListener)
- data = MediaTestUtils.emptyMediaData.copy(
+ data =
+ MediaTestUtils.emptyMediaData.copy(
song = TITLE,
packageName = PACKAGE_NAME,
- token = token)
+ token = token
+ )
}
@After
fun tearDown() {
- Settings.Global.putInt(context.contentResolver,
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsSetting)
- Settings.Secure.putInt(context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RESUME, originalResumeSetting)
+ Settings.Global.putInt(
+ context.contentResolver,
+ Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS,
+ originalQsSetting
+ )
+ Settings.Secure.putInt(
+ context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RESUME,
+ originalResumeSetting
+ )
}
@Test
fun testWhenNoResumption_doesNothing() {
- Settings.Secure.putInt(context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+ Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
// When listener is created, we do NOT register a user change listener
- val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService,
- resumeBrowserFactory, dumpManager, clock)
+ val listener =
+ MediaResumeListener(
+ context,
+ broadcastDispatcher,
+ executor,
+ tunerService,
+ resumeBrowserFactory,
+ dumpManager,
+ clock
+ )
listener.setManager(mediaDataManager)
- verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver),
- any(), any(), any(), anyInt(), any())
+ verify(broadcastDispatcher, never())
+ .registerReceiver(eq(listener.userChangeReceiver), any(), any(), any(), anyInt(), any())
// When data is loaded, we do NOT execute or update anything
listener.onMediaDataLoaded(KEY, OLD_KEY, data)
@@ -170,9 +206,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
fun testOnLoad_checksForResume_badService() {
setUpMbsWithValidResolveInfo()
- whenever(resumeBrowser.testConnection()).thenAnswer {
- callbackCaptor.value.onError()
- }
+ whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() }
// When media data is loaded that has not been checked yet, and does not have a MBS
resumeListener.onMediaDataLoaded(KEY, null, data)
@@ -226,7 +260,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
// But we do not tell it to add new controls
verify(mediaDataManager, never())
- .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any())
+ .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any())
}
@Test
@@ -253,8 +287,15 @@ class MediaResumeListenerTest : SysuiTestCase() {
// Make sure broadcast receiver is registered
resumeListener.setManager(mediaDataManager)
- verify(broadcastDispatcher).registerReceiver(eq(resumeListener.userChangeReceiver),
- any(), any(), any(), anyInt(), any())
+ verify(broadcastDispatcher)
+ .registerReceiver(
+ eq(resumeListener.userChangeReceiver),
+ any(),
+ any(),
+ any(),
+ anyInt(),
+ any()
+ )
// When we get an unlock event
val intent = Intent(Intent.ACTION_USER_UNLOCKED)
@@ -264,8 +305,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
verify(resumeBrowser, times(3)).findRecentMedia()
// Then since the mock service found media, the manager should be informed
- verify(mediaDataManager, times(3)).addResumptionControls(anyInt(),
- any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
+ verify(mediaDataManager, times(3))
+ .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
}
@Test
@@ -304,12 +345,14 @@ class MediaResumeListenerTest : SysuiTestCase() {
// Then we save an update with the current time
verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
- componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
- ?.dropLastWhile { it.isEmpty() }.forEach {
- val result = it.split("/")
- assertThat(result.size).isEqualTo(3)
- assertThat(result[2].toLong()).isEqualTo(currentTime)
- }
+ componentCaptor.value
+ .split(ResumeMediaBrowser.DELIMITER.toRegex())
+ .dropLastWhile { it.isEmpty() }
+ .forEach {
+ val result = it.split("/")
+ assertThat(result.size).isEqualTo(3)
+ assertThat(result[2].toLong()).isEqualTo(currentTime)
+ }
verify(sharedPrefsEditor, times(1)).apply()
}
@@ -328,8 +371,16 @@ class MediaResumeListenerTest : SysuiTestCase() {
val lastPlayed = clock.currentTimeMillis()
val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
- val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
- tunerService, resumeBrowserFactory, dumpManager, clock)
+ val resumeListener =
+ MediaResumeListener(
+ mockContext,
+ broadcastDispatcher,
+ executor,
+ tunerService,
+ resumeBrowserFactory,
+ dumpManager,
+ clock
+ )
resumeListener.setManager(mediaDataManager)
mediaDataManager.addListener(resumeListener)
@@ -339,8 +390,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
// We add its resume controls
verify(resumeBrowser, times(1)).findRecentMedia()
- verify(mediaDataManager, times(1)).addResumptionControls(anyInt(),
- any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
+ verify(mediaDataManager, times(1))
+ .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
}
@Test
@@ -349,8 +400,16 @@ class MediaResumeListenerTest : SysuiTestCase() {
val lastPlayed = clock.currentTimeMillis() - RESUME_MEDIA_TIMEOUT - 100
val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
- val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
- tunerService, resumeBrowserFactory, dumpManager, clock)
+ val resumeListener =
+ MediaResumeListener(
+ mockContext,
+ broadcastDispatcher,
+ executor,
+ tunerService,
+ resumeBrowserFactory,
+ dumpManager,
+ clock
+ )
resumeListener.setManager(mediaDataManager)
mediaDataManager.addListener(resumeListener)
@@ -360,8 +419,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
// We do not try to add resume controls
verify(resumeBrowser, times(0)).findRecentMedia()
- verify(mediaDataManager, times(0)).addResumptionControls(anyInt(),
- any(), any(), any(), any(), any(), any())
+ verify(mediaDataManager, times(0))
+ .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any())
}
@Test
@@ -380,8 +439,16 @@ class MediaResumeListenerTest : SysuiTestCase() {
val lastPlayed = currentTime - 1000
val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
- val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
- tunerService, resumeBrowserFactory, dumpManager, clock)
+ val resumeListener =
+ MediaResumeListener(
+ mockContext,
+ broadcastDispatcher,
+ executor,
+ tunerService,
+ resumeBrowserFactory,
+ dumpManager,
+ clock
+ )
resumeListener.setManager(mediaDataManager)
mediaDataManager.addListener(resumeListener)
@@ -391,12 +458,14 @@ class MediaResumeListenerTest : SysuiTestCase() {
// Then we store the new lastPlayed time
verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
- componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
- ?.dropLastWhile { it.isEmpty() }.forEach {
- val result = it.split("/")
- assertThat(result.size).isEqualTo(3)
- assertThat(result[2].toLong()).isEqualTo(currentTime)
- }
+ componentCaptor.value
+ .split(ResumeMediaBrowser.DELIMITER.toRegex())
+ .dropLastWhile { it.isEmpty() }
+ .forEach {
+ val result = it.split("/")
+ assertThat(result.size).isEqualTo(3)
+ assertThat(result[2].toLong()).isEqualTo(currentTime)
+ }
verify(sharedPrefsEditor, times(1)).apply()
}
@@ -417,9 +486,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
setUpMbsWithValidResolveInfo()
// Set up mocks to return with an error
- whenever(resumeBrowser.testConnection()).thenAnswer {
- callbackCaptor.value.onError()
- }
+ whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() }
resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data)
executor.runAllReady()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt
index dafaa6b93696..a04cfd46588b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.resume
import android.content.ComponentName
import android.content.Context
@@ -37,8 +37,8 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
private const val PACKAGE_NAME = "package"
private const val CLASS_NAME = "class"
@@ -47,7 +47,9 @@ private const val MEDIA_ID = "media ID"
private const val ROOT = "media browser root"
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
private fun <T> any(): T = Mockito.any<T>()
@SmallTest
@@ -57,10 +59,8 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
private lateinit var resumeBrowser: TestableResumeMediaBrowser
private val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
- private val description = MediaDescription.Builder()
- .setTitle(TITLE)
- .setMediaId(MEDIA_ID)
- .build()
+ private val description =
+ MediaDescription.Builder().setTitle(TITLE).setMediaId(MEDIA_ID).build()
@Mock lateinit var callback: ResumeMediaBrowser.Callback
@Mock lateinit var listener: MediaResumeListener
@@ -81,19 +81,20 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(browserFactory.create(any(), capture(connectionCallback), any()))
- .thenReturn(browser)
+ .thenReturn(browser)
whenever(mediaController.transportControls).thenReturn(transportControls)
whenever(mediaController.sessionToken).thenReturn(token)
- resumeBrowser = TestableResumeMediaBrowser(
- context,
- callback,
- component,
- browserFactory,
- logger,
- mediaController
- )
+ resumeBrowser =
+ TestableResumeMediaBrowser(
+ context,
+ callback,
+ component,
+ browserFactory,
+ logger,
+ mediaController
+ )
}
@Test
@@ -329,30 +330,20 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
verify(oldBrowser).disconnect()
}
- /**
- * Helper function to mock a failed connection
- */
+ /** Helper function to mock a failed connection */
private fun setupBrowserFailed() {
- whenever(browser.connect()).thenAnswer {
- connectionCallback.value.onConnectionFailed()
- }
+ whenever(browser.connect()).thenAnswer { connectionCallback.value.onConnectionFailed() }
}
- /**
- * Helper function to mock a successful connection only
- */
+ /** Helper function to mock a successful connection only */
private fun setupBrowserConnection() {
- whenever(browser.connect()).thenAnswer {
- connectionCallback.value.onConnected()
- }
+ whenever(browser.connect()).thenAnswer { connectionCallback.value.onConnected() }
whenever(browser.isConnected()).thenReturn(true)
whenever(browser.getRoot()).thenReturn(ROOT)
whenever(browser.sessionToken).thenReturn(token)
}
- /**
- * Helper function to mock a successful connection, but no media results
- */
+ /** Helper function to mock a successful connection, but no media results */
private fun setupBrowserConnectionNoResults() {
setupBrowserConnection()
whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
@@ -360,9 +351,7 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
}
}
- /**
- * Helper function to mock a successful connection, but no playable results
- */
+ /** Helper function to mock a successful connection, but no playable results */
private fun setupBrowserConnectionNotPlayable() {
setupBrowserConnection()
@@ -373,9 +362,7 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
}
}
- /**
- * Helper function to mock a successful connection with playable media
- */
+ /** Helper function to mock a successful connection with playable media */
private fun setupBrowserConnectionValidMedia() {
setupBrowserConnection()
@@ -387,9 +374,7 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
}
}
- /**
- * Override so media controller use is testable
- */
+ /** Override so media controller use is testable */
private class TestableResumeMediaBrowser(
context: Context,
callback: Callback,
@@ -403,4 +388,4 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
return fakeController
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/AnimationBindHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt
index e4cab1810822..99f56b16ab8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/AnimationBindHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt
@@ -14,26 +14,26 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
-import org.mockito.Mockito.`when` as whenever
import android.graphics.drawable.Animatable2
import android.graphics.drawable.Drawable
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
-import junit.framework.Assert.assertTrue
import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
import org.junit.After
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.times
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
@SmallTest
@@ -56,8 +56,7 @@ class AnimationBindHandlerTest : SysuiTestCase() {
handler = AnimationBindHandler()
}
- @After
- fun tearDown() {}
+ @After fun tearDown() {}
@Test
fun registerNoAnimations_executeCallbackImmediately() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
index f56d42ec3fb4..5bb74e5a31f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.animation.ValueAnimator
import android.graphics.Color
@@ -22,6 +22,8 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.models.GutsViewHolder
+import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.monet.ColorScheme
import junit.framework.Assert.assertEquals
import org.junit.After
@@ -67,21 +69,18 @@ class ColorSchemeTransitionTest : SysuiTestCase() {
animatingColorTransitionFactory = { _, _, _ -> mockAnimatingTransition }
whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR)
- colorSchemeTransition = ColorSchemeTransition(
- context, mediaViewHolder, animatingColorTransitionFactory
- )
+ colorSchemeTransition =
+ ColorSchemeTransition(context, mediaViewHolder, animatingColorTransitionFactory)
- colorTransition = object : AnimatingColorTransition(
- DEFAULT_COLOR, extractColor, applyColor
- ) {
- override fun buildAnimator(): ValueAnimator {
- return valueAnimator
+ colorTransition =
+ object : AnimatingColorTransition(DEFAULT_COLOR, extractColor, applyColor) {
+ override fun buildAnimator(): ValueAnimator {
+ return valueAnimator
+ }
}
- }
}
- @After
- fun tearDown() {}
+ @After fun tearDown() {}
@Test
fun testColorTransition_nullColorScheme_keepsDefault() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
index c41fac71a179..20260069c943 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.provider.Settings
import android.test.suitebuilder.annotation.SmallTest
@@ -48,17 +48,12 @@ import org.mockito.junit.MockitoJUnit
@TestableLooper.RunWithLooper
class KeyguardMediaControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var mediaHost: MediaHost
- @Mock
- private lateinit var bypassController: KeyguardBypassController
- @Mock
- private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock
- private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var mediaHost: MediaHost
+ @Mock private lateinit var bypassController: KeyguardBypassController
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var configurationController: ConfigurationController
- @JvmField @Rule
- val mockito = MockitoJUnit.rule()
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
private val mediaContainerView: MediaContainerView = MediaContainerView(context, null)
private val hostView = UniqueObjectHostView(context)
@@ -76,15 +71,16 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
hostView.layoutParams = FrameLayout.LayoutParams(100, 100)
testableLooper = TestableLooper.get(this)
fakeHandler = FakeHandler(testableLooper.looper)
- keyguardMediaController = KeyguardMediaController(
- mediaHost,
- bypassController,
- statusBarStateController,
- context,
- settings,
- fakeHandler,
- configurationController,
- )
+ keyguardMediaController =
+ KeyguardMediaController(
+ mediaHost,
+ bypassController,
+ statusBarStateController,
+ context,
+ settings,
+ fakeHandler,
+ configurationController,
+ )
keyguardMediaController.attachSinglePaneContainer(mediaContainerView)
keyguardMediaController.useSplitShade = false
}
@@ -153,8 +149,10 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
keyguardMediaController.useSplitShade = true
- assertTrue("HostView wasn't attached to the split pane container",
- splitShadeContainer.childCount == 1)
+ assertTrue(
+ "HostView wasn't attached to the split pane container",
+ splitShadeContainer.childCount == 1
+ )
}
@Test
@@ -162,8 +160,10 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
val splitShadeContainer = FrameLayout(context)
keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
- assertTrue("HostView wasn't attached to the single pane container",
- mediaContainerView.childCount == 1)
+ assertTrue(
+ "HostView wasn't attached to the single pane container",
+ mediaContainerView.childCount == 1
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
new file mode 100644
index 000000000000..c8e8943689c9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -0,0 +1,645 @@
+/*
+ * 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.media.controls.ui
+
+import android.app.PendingIntent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.controls.MediaTestUtils
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.PAGINATION_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.TRANSFORM_BEZIER
+import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS
+import com.android.systemui.media.controls.util.MediaUiEventLogger
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
+import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import javax.inject.Provider
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+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.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+private val DATA = MediaTestUtils.emptyMediaData
+
+private val SMARTSPACE_KEY = "smartspace"
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class MediaCarouselControllerTest : SysuiTestCase() {
+
+ @Mock lateinit var mediaControlPanelFactory: Provider<MediaControlPanel>
+ @Mock lateinit var panel: MediaControlPanel
+ @Mock lateinit var visualStabilityProvider: VisualStabilityProvider
+ @Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
+ @Mock lateinit var mediaHostState: MediaHostState
+ @Mock lateinit var activityStarter: ActivityStarter
+ @Mock @Main private lateinit var executor: DelayableExecutor
+ @Mock lateinit var mediaDataManager: MediaDataManager
+ @Mock lateinit var configurationController: ConfigurationController
+ @Mock lateinit var falsingCollector: FalsingCollector
+ @Mock lateinit var falsingManager: FalsingManager
+ @Mock lateinit var dumpManager: DumpManager
+ @Mock lateinit var logger: MediaUiEventLogger
+ @Mock lateinit var debugLogger: MediaCarouselControllerLogger
+ @Mock lateinit var mediaViewController: MediaViewController
+ @Mock lateinit var smartspaceMediaData: SmartspaceMediaData
+ @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
+ @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
+
+ private val clock = FakeSystemClock()
+ private lateinit var mediaCarouselController: MediaCarouselController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mediaCarouselController =
+ MediaCarouselController(
+ context,
+ mediaControlPanelFactory,
+ visualStabilityProvider,
+ mediaHostStatesManager,
+ activityStarter,
+ clock,
+ executor,
+ mediaDataManager,
+ configurationController,
+ falsingCollector,
+ falsingManager,
+ dumpManager,
+ logger,
+ debugLogger
+ )
+ verify(mediaDataManager).addListener(capture(listener))
+ verify(visualStabilityProvider)
+ .addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
+ whenever(mediaControlPanelFactory.get()).thenReturn(panel)
+ whenever(panel.mediaViewController).thenReturn(mediaViewController)
+ whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
+ MediaPlayerData.clear()
+ }
+
+ @Test
+ fun testPlayerOrdering() {
+ // Test values: key, data, last active time
+ val playingLocal =
+ Triple(
+ "playing local",
+ DATA.copy(
+ active = true,
+ isPlaying = true,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = false
+ ),
+ 4500L
+ )
+
+ val playingCast =
+ Triple(
+ "playing cast",
+ DATA.copy(
+ active = true,
+ isPlaying = true,
+ playbackLocation = MediaData.PLAYBACK_CAST_LOCAL,
+ resumption = false
+ ),
+ 5000L
+ )
+
+ val pausedLocal =
+ Triple(
+ "paused local",
+ DATA.copy(
+ active = true,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = false
+ ),
+ 1000L
+ )
+
+ val pausedCast =
+ Triple(
+ "paused cast",
+ DATA.copy(
+ active = true,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_CAST_LOCAL,
+ resumption = false
+ ),
+ 2000L
+ )
+
+ val playingRcn =
+ Triple(
+ "playing RCN",
+ DATA.copy(
+ active = true,
+ isPlaying = true,
+ playbackLocation = MediaData.PLAYBACK_CAST_REMOTE,
+ resumption = false
+ ),
+ 5000L
+ )
+
+ val pausedRcn =
+ Triple(
+ "paused RCN",
+ DATA.copy(
+ active = true,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_CAST_REMOTE,
+ resumption = false
+ ),
+ 5000L
+ )
+
+ val active =
+ Triple(
+ "active",
+ DATA.copy(
+ active = true,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = true
+ ),
+ 250L
+ )
+
+ val resume1 =
+ Triple(
+ "resume 1",
+ DATA.copy(
+ active = false,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = true
+ ),
+ 500L
+ )
+
+ val resume2 =
+ Triple(
+ "resume 2",
+ DATA.copy(
+ active = false,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = true
+ ),
+ 1000L
+ )
+
+ val activeMoreRecent =
+ Triple(
+ "active more recent",
+ DATA.copy(
+ active = false,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = true,
+ lastActive = 2L
+ ),
+ 1000L
+ )
+
+ val activeLessRecent =
+ Triple(
+ "active less recent",
+ DATA.copy(
+ active = false,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = true,
+ lastActive = 1L
+ ),
+ 1000L
+ )
+ // Expected ordering for media players:
+ // Actively playing local sessions
+ // Actively playing cast sessions
+ // Paused local and cast sessions, by last active
+ // RCNs
+ // Resume controls, by last active
+
+ val expected =
+ listOf(
+ playingLocal,
+ playingCast,
+ pausedCast,
+ pausedLocal,
+ playingRcn,
+ pausedRcn,
+ active,
+ resume2,
+ resume1
+ )
+
+ expected.forEach {
+ clock.setCurrentTimeMillis(it.third)
+ MediaPlayerData.addMediaPlayer(
+ it.first,
+ it.second.copy(notificationKey = it.first),
+ panel,
+ clock,
+ isSsReactivated = false
+ )
+ }
+
+ for ((index, key) in MediaPlayerData.playerKeys().withIndex()) {
+ assertEquals(expected.get(index).first, key.data.notificationKey)
+ }
+
+ for ((index, key) in MediaPlayerData.visiblePlayerKeys().withIndex()) {
+ assertEquals(expected.get(index).first, key.data.notificationKey)
+ }
+ }
+
+ @Test
+ fun testOrderWithSmartspace_prioritized() {
+ testPlayerOrdering()
+
+ // If smartspace is prioritized
+ MediaPlayerData.addMediaRecommendation(
+ SMARTSPACE_KEY,
+ EMPTY_SMARTSPACE_MEDIA_DATA,
+ panel,
+ true,
+ clock
+ )
+
+ // Then it should be shown immediately after any actively playing controls
+ assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
+ }
+
+ @Test
+ fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
+ testPlayerOrdering()
+
+ // If smartspace is prioritized
+ listener.value.onSmartspaceMediaDataLoaded(
+ SMARTSPACE_KEY,
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
+ true
+ )
+
+ // Then it should be shown immediately after any actively playing controls
+ assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
+ assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec)
+ }
+
+ @Test
+ fun testOrderWithSmartspace_notPrioritized() {
+ testPlayerOrdering()
+
+ // If smartspace is not prioritized
+ MediaPlayerData.addMediaRecommendation(
+ SMARTSPACE_KEY,
+ EMPTY_SMARTSPACE_MEDIA_DATA,
+ panel,
+ false,
+ clock
+ )
+
+ // Then it should be shown at the end of the carousel's active entries
+ val idx = MediaPlayerData.playerKeys().count { it.data.active } - 1
+ assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
+ }
+
+ @Test
+ fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
+ testPlayerOrdering()
+ // playing paused player
+ listener.value.onMediaDataLoaded(
+ "paused local",
+ "paused local",
+ DATA.copy(
+ active = true,
+ isPlaying = true,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = false
+ )
+ )
+ listener.value.onMediaDataLoaded(
+ "playing local",
+ "playing local",
+ DATA.copy(
+ active = true,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = true
+ )
+ )
+
+ assertEquals(
+ MediaPlayerData.getMediaPlayerIndex("paused local"),
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ )
+ // paused player order should stays the same in visibleMediaPLayer map.
+ // paused player order should be first in mediaPlayer map.
+ assertEquals(
+ MediaPlayerData.visiblePlayerKeys().elementAt(3),
+ MediaPlayerData.playerKeys().elementAt(0)
+ )
+ }
+ @Test
+ fun testSwipeDismiss_logged() {
+ mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
+
+ verify(logger).logSwipeDismiss()
+ }
+
+ @Test
+ fun testSettingsButton_logged() {
+ mediaCarouselController.settingsButton.callOnClick()
+
+ verify(logger).logCarouselSettings()
+ }
+
+ @Test
+ fun testLocationChangeQs_logged() {
+ mediaCarouselController.onDesiredLocationChanged(
+ MediaHierarchyManager.LOCATION_QS,
+ mediaHostState,
+ animate = false
+ )
+ verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
+ }
+
+ @Test
+ fun testLocationChangeQqs_logged() {
+ mediaCarouselController.onDesiredLocationChanged(
+ MediaHierarchyManager.LOCATION_QQS,
+ mediaHostState,
+ animate = false
+ )
+ verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
+ }
+
+ @Test
+ fun testLocationChangeLockscreen_logged() {
+ mediaCarouselController.onDesiredLocationChanged(
+ MediaHierarchyManager.LOCATION_LOCKSCREEN,
+ mediaHostState,
+ animate = false
+ )
+ verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
+ }
+
+ @Test
+ fun testLocationChangeDream_logged() {
+ mediaCarouselController.onDesiredLocationChanged(
+ MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
+ mediaHostState,
+ animate = false
+ )
+ verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
+ }
+
+ @Test
+ fun testRecommendationRemoved_logged() {
+ val packageName = "smartspace package"
+ val instanceId = InstanceId.fakeInstanceId(123)
+
+ val smartspaceData =
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = packageName, instanceId = instanceId)
+ MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, smartspaceData, panel, true, clock)
+ mediaCarouselController.removePlayer(SMARTSPACE_KEY)
+
+ verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
+ }
+
+ @Test
+ fun testMediaLoaded_ScrollToActivePlayer() {
+ listener.value.onMediaDataLoaded(
+ "playing local",
+ null,
+ DATA.copy(
+ active = true,
+ isPlaying = true,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = false
+ )
+ )
+ listener.value.onMediaDataLoaded(
+ "paused local",
+ null,
+ DATA.copy(
+ active = true,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = false
+ )
+ )
+ // adding a media recommendation card.
+ listener.value.onSmartspaceMediaDataLoaded(
+ SMARTSPACE_KEY,
+ EMPTY_SMARTSPACE_MEDIA_DATA,
+ false
+ )
+ mediaCarouselController.shouldScrollToKey = true
+ // switching between media players.
+ listener.value.onMediaDataLoaded(
+ "playing local",
+ "playing local",
+ DATA.copy(
+ active = true,
+ isPlaying = false,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = true
+ )
+ )
+ listener.value.onMediaDataLoaded(
+ "paused local",
+ "paused local",
+ DATA.copy(
+ active = true,
+ isPlaying = true,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = false
+ )
+ )
+
+ assertEquals(
+ MediaPlayerData.getMediaPlayerIndex("paused local"),
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ )
+ }
+
+ @Test
+ fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
+ listener.value.onSmartspaceMediaDataLoaded(
+ SMARTSPACE_KEY,
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true),
+ false
+ )
+ listener.value.onMediaDataLoaded(
+ "playing local",
+ null,
+ DATA.copy(
+ active = true,
+ isPlaying = true,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = false
+ )
+ )
+
+ var playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
+ assertEquals(
+ playerIndex,
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ )
+ assertEquals(playerIndex, 0)
+
+ // Replaying the same media player one more time.
+ // And check that the card stays in its position.
+ mediaCarouselController.shouldScrollToKey = true
+ listener.value.onMediaDataLoaded(
+ "playing local",
+ null,
+ DATA.copy(
+ active = true,
+ isPlaying = true,
+ playbackLocation = MediaData.PLAYBACK_LOCAL,
+ resumption = false,
+ packageName = "PACKAGE_NAME"
+ )
+ )
+ playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
+ assertEquals(playerIndex, 0)
+ }
+
+ @Test
+ fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
+ var result = false
+ mediaCarouselController.updateHostVisibility = { result = true }
+
+ whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
+ listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
+
+ assertEquals(true, result)
+ }
+
+ @Test
+ fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
+ var result = false
+ mediaCarouselController.updateHostVisibility = { result = true }
+
+ whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
+ listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
+ assertEquals(false, result)
+
+ visualStabilityCallback.value.onReorderingAllowed()
+ assertEquals(true, result)
+ }
+
+ @Test
+ fun testGetCurrentVisibleMediaContentIntent() {
+ val clickIntent1 = mock(PendingIntent::class.java)
+ val player1 = Triple("player1", DATA.copy(clickIntent = clickIntent1), 1000L)
+ clock.setCurrentTimeMillis(player1.third)
+ MediaPlayerData.addMediaPlayer(
+ player1.first,
+ player1.second.copy(notificationKey = player1.first),
+ panel,
+ clock,
+ isSsReactivated = false
+ )
+
+ assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1)
+
+ val clickIntent2 = mock(PendingIntent::class.java)
+ val player2 = Triple("player2", DATA.copy(clickIntent = clickIntent2), 2000L)
+ clock.setCurrentTimeMillis(player2.third)
+ MediaPlayerData.addMediaPlayer(
+ player2.first,
+ player2.second.copy(notificationKey = player2.first),
+ panel,
+ clock,
+ isSsReactivated = false
+ )
+
+ // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
+ // added to the front because it was active more recently.
+ assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
+
+ val clickIntent3 = mock(PendingIntent::class.java)
+ val player3 = Triple("player3", DATA.copy(clickIntent = clickIntent3), 500L)
+ clock.setCurrentTimeMillis(player3.third)
+ MediaPlayerData.addMediaPlayer(
+ player3.first,
+ player3.second.copy(notificationKey = player3.first),
+ panel,
+ clock,
+ isSsReactivated = false
+ )
+
+ // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
+ // added to the end because it was active less recently.
+ assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
+ }
+
+ @Test
+ fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
+ val delta = 0.0001F
+ val paginationSquishMiddle =
+ TRANSFORM_BEZIER.getInterpolation(
+ (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
+ )
+ val paginationSquishEnd =
+ TRANSFORM_BEZIER.getInterpolation(
+ (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION
+ )
+ whenever(mediaHostStatesManager.mediaHostStates)
+ .thenReturn(mutableMapOf(LOCATION_QS to mediaHostState))
+ whenever(mediaHostState.visible).thenReturn(true)
+ mediaCarouselController.currentEndLocation = LOCATION_QS
+ whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle)
+ mediaCarouselController.updatePageIndicatorAlpha()
+ assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta)
+
+ whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd)
+ mediaCarouselController.updatePageIndicatorAlpha()
+ assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 7de5719c03ec..584305334b6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.animation.Animator
import android.animation.AnimatorSet
@@ -59,7 +59,20 @@ 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.controls.MediaTestUtils
+import com.android.systemui.media.controls.models.GutsViewHolder
+import com.android.systemui.media.controls.models.player.MediaAction
+import com.android.systemui.media.controls.models.player.MediaButton
+import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.player.MediaDeviceData
+import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.models.player.SeekBarViewModel
+import com.android.systemui.media.controls.models.recommendation.KEY_SMARTSPACE_APP_NAME
+import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
+import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -164,8 +177,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
private lateinit var session: MediaSession
private lateinit var device: MediaDeviceData
- private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME, null,
- showBroadcastButton = false)
+ 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
@@ -212,24 +225,27 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE)
context.setMockPackageManager(packageManager)
- player = object : MediaControlPanel(
- context,
- bgExecutor,
- mainExecutor,
- activityStarter,
- broadcastSender,
- mediaViewController,
- seekBarViewModel,
- Lazy { mediaDataManager },
- mediaOutputDialogFactory,
- mediaCarouselController,
- falsingManager,
- clock,
- logger,
- keyguardStateController,
- activityIntentHelper,
- lockscreenUserManager,
- broadcastDialogController) {
+ player =
+ object :
+ MediaControlPanel(
+ context,
+ bgExecutor,
+ mainExecutor,
+ activityStarter,
+ broadcastSender,
+ mediaViewController,
+ seekBarViewModel,
+ Lazy { mediaDataManager },
+ mediaOutputDialogFactory,
+ mediaCarouselController,
+ falsingManager,
+ clock,
+ logger,
+ keyguardStateController,
+ activityIntentHelper,
+ lockscreenUserManager,
+ broadcastDialogController
+ ) {
override fun loadAnimator(
animId: Int,
otionInterpolator: Interpolator,
@@ -250,18 +266,20 @@ public class MediaControlPanelTest : SysuiTestCase() {
// Set valid recommendation data
val extras = Bundle()
extras.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME)
- val intent = Intent().apply {
- putExtras(extras)
- setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- }
+ val intent =
+ Intent().apply {
+ putExtras(extras)
+ setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
whenever(smartspaceAction.intent).thenReturn(intent)
whenever(smartspaceAction.extras).thenReturn(extras)
- smartspaceData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- packageName = PACKAGE,
- instanceId = instanceId,
- recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction),
- cardAction = smartspaceAction
- )
+ smartspaceData =
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+ packageName = PACKAGE,
+ instanceId = instanceId,
+ recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction),
+ cardAction = smartspaceAction
+ )
}
private fun initGutsViewHolderMocks() {
@@ -279,36 +297,39 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
private fun initDeviceMediaData(shouldShowBroadcastButton: Boolean, name: String) {
- device = MediaDeviceData(true, null, name, null,
- showBroadcastButton = shouldShowBroadcastButton)
+ 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())
- }
+ 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(
+ mediaData =
+ MediaTestUtils.emptyMediaData.copy(
artist = ARTIST,
song = TITLE,
packageName = PACKAGE,
token = session.sessionToken,
device = device,
- instanceId = instanceId)
+ instanceId = instanceId
+ )
}
- /**
- * Initialize elements in media view holder
- */
+ /** Initialize elements in media view holder */
private fun initMediaViewHolderMocks() {
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
@@ -349,7 +370,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
action1.id,
action2.id,
action3.id,
- action4.id)
+ action4.id
+ )
}
whenever(viewHolder.player).thenReturn(view)
@@ -394,9 +416,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
}
- /**
- * Initialize elements for the recommendation view holder
- */
+ /** Initialize elements for the recommendation view holder */
private fun initRecommendationViewHolderMocks() {
recTitle1 = TextView(context)
recTitle2 = TextView(context)
@@ -419,9 +439,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
.thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
whenever(recommendationViewHolder.mediaTitles)
.thenReturn(listOf(recTitle1, recTitle2, recTitle3))
- whenever(recommendationViewHolder.mediaSubtitles).thenReturn(
- listOf(recSubtitle1, recSubtitle2, recSubtitle3)
- )
+ whenever(recommendationViewHolder.mediaSubtitles)
+ .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
@@ -453,12 +472,13 @@ public class MediaControlPanelTest : SysuiTestCase() {
fun bindSemanticActions() {
val icon = context.getDrawable(android.R.drawable.ic_media_play)
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
- val semanticActions = MediaButton(
- playOrPause = MediaAction(icon, Runnable {}, "play", bg),
- nextOrCustom = MediaAction(icon, Runnable {}, "next", bg),
- custom0 = MediaAction(icon, null, "custom 0", bg),
- custom1 = MediaAction(icon, null, "custom 1", bg)
- )
+ val semanticActions =
+ MediaButton(
+ playOrPause = MediaAction(icon, Runnable {}, "play", bg),
+ nextOrCustom = MediaAction(icon, Runnable {}, "next", bg),
+ custom0 = MediaAction(icon, null, "custom 0", bg),
+ custom1 = MediaAction(icon, null, "custom 1", bg)
+ )
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
player.bindPlayer(state, PACKAGE)
@@ -501,15 +521,16 @@ public class MediaControlPanelTest : SysuiTestCase() {
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
// Setup button state: no prev or next button and their slots reserved
- val semanticActions = MediaButton(
- playOrPause = MediaAction(icon, Runnable {}, "play", bg),
- nextOrCustom = null,
- prevOrCustom = null,
- custom0 = MediaAction(icon, null, "custom 0", bg),
- custom1 = MediaAction(icon, null, "custom 1", bg),
- false,
- true
- )
+ val semanticActions =
+ MediaButton(
+ playOrPause = MediaAction(icon, Runnable {}, "play", bg),
+ nextOrCustom = null,
+ prevOrCustom = null,
+ custom0 = MediaAction(icon, null, "custom 0", bg),
+ custom1 = MediaAction(icon, null, "custom 1", bg),
+ false,
+ true
+ )
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -530,15 +551,16 @@ public class MediaControlPanelTest : SysuiTestCase() {
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
// Setup button state: no prev or next button and their slots reserved
- val semanticActions = MediaButton(
- playOrPause = MediaAction(icon, Runnable {}, "play", bg),
- nextOrCustom = null,
- prevOrCustom = null,
- custom0 = MediaAction(icon, null, "custom 0", bg),
- custom1 = MediaAction(icon, null, "custom 1", bg),
- true,
- false
- )
+ val semanticActions =
+ MediaButton(
+ playOrPause = MediaAction(icon, Runnable {}, "play", bg),
+ nextOrCustom = null,
+ prevOrCustom = null,
+ custom0 = MediaAction(icon, null, "custom 0", bg),
+ custom1 = MediaAction(icon, null, "custom 1", bg),
+ true,
+ false
+ )
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -646,10 +668,11 @@ public class MediaControlPanelTest : SysuiTestCase() {
useRealConstraintSets()
val icon = context.getDrawable(android.R.drawable.ic_media_play)
- val semanticActions = MediaButton(
- playOrPause = MediaAction(icon, Runnable {}, "play", null),
- nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
- )
+ val semanticActions =
+ MediaButton(
+ playOrPause = MediaAction(icon, Runnable {}, "play", null),
+ nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
+ )
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -719,9 +742,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
useRealConstraintSets()
val icon = context.getDrawable(android.R.drawable.ic_media_play)
- val semanticActions = MediaButton(
- nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
- )
+ val semanticActions =
+ MediaButton(nextOrCustom = MediaAction(icon, Runnable {}, "next", null))
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -736,10 +758,11 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bind_notScrubbing_scrubbingViewsGone() {
val icon = context.getDrawable(android.R.drawable.ic_media_play)
- val semanticActions = MediaButton(
- prevOrCustom = MediaAction(icon, {}, "prev", null),
- nextOrCustom = MediaAction(icon, {}, "next", null)
- )
+ val semanticActions =
+ MediaButton(
+ prevOrCustom = MediaAction(icon, {}, "prev", null),
+ nextOrCustom = MediaAction(icon, {}, "next", null)
+ )
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -770,10 +793,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
val icon = context.getDrawable(android.R.drawable.ic_media_play)
- val semanticActions = MediaButton(
- prevOrCustom = null,
- nextOrCustom = MediaAction(icon, {}, "next", null)
- )
+ val semanticActions =
+ MediaButton(prevOrCustom = null, nextOrCustom = MediaAction(icon, {}, "next", null))
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
player.bindPlayer(state, PACKAGE)
@@ -790,10 +811,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
val icon = context.getDrawable(android.R.drawable.ic_media_play)
- val semanticActions = MediaButton(
- prevOrCustom = MediaAction(icon, {}, "prev", null),
- nextOrCustom = null
- )
+ val semanticActions =
+ MediaButton(prevOrCustom = MediaAction(icon, {}, "prev", null), nextOrCustom = null)
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
player.bindPlayer(state, PACKAGE)
@@ -810,10 +829,11 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun setIsScrubbing_true_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
val icon = context.getDrawable(android.R.drawable.ic_media_play)
- val semanticActions = MediaButton(
- prevOrCustom = MediaAction(icon, {}, "prev", null),
- nextOrCustom = MediaAction(icon, {}, "next", null)
- )
+ val semanticActions =
+ MediaButton(
+ prevOrCustom = MediaAction(icon, {}, "prev", null),
+ nextOrCustom = MediaAction(icon, {}, "next", null)
+ )
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
player.bindPlayer(state, PACKAGE)
@@ -832,10 +852,11 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun setIsScrubbing_trueThenFalse_scrubbingTimeGoneAtEnd() {
val icon = context.getDrawable(android.R.drawable.ic_media_play)
- val semanticActions = MediaButton(
- prevOrCustom = MediaAction(icon, {}, "prev", null),
- nextOrCustom = MediaAction(icon, {}, "next", null)
- )
+ val semanticActions =
+ MediaButton(
+ prevOrCustom = MediaAction(icon, {}, "prev", null),
+ nextOrCustom = MediaAction(icon, {}, "next", null)
+ )
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -859,18 +880,20 @@ public class MediaControlPanelTest : SysuiTestCase() {
fun bindNotificationActions() {
val icon = context.getDrawable(android.R.drawable.ic_media_play)
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
- val actions = listOf(
- MediaAction(icon, Runnable {}, "previous", bg),
- MediaAction(icon, Runnable {}, "play", bg),
- MediaAction(icon, null, "next", bg),
- MediaAction(icon, null, "custom 0", bg),
- MediaAction(icon, Runnable {}, "custom 1", bg)
- )
- val state = mediaData.copy(
- actions = actions,
- actionsToShowInCompact = listOf(1, 2),
- semanticActions = null
- )
+ val actions =
+ listOf(
+ MediaAction(icon, Runnable {}, "previous", bg),
+ MediaAction(icon, Runnable {}, "play", bg),
+ MediaAction(icon, null, "next", bg),
+ MediaAction(icon, null, "custom 0", bg),
+ MediaAction(icon, Runnable {}, "custom 1", bg)
+ )
+ val state =
+ mediaData.copy(
+ actions = actions,
+ actionsToShowInCompact = listOf(1, 2),
+ semanticActions = null
+ )
player.attachPlayer(viewHolder)
player.bindPlayer(state, PACKAGE)
@@ -918,15 +941,12 @@ public class MediaControlPanelTest : SysuiTestCase() {
val icon = context.getDrawable(R.drawable.ic_media_play)
val bg = context.getDrawable(R.drawable.ic_media_play_container)
- val semanticActions0 = MediaButton(
- playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null)
- )
- val semanticActions1 = MediaButton(
- playOrPause = MediaAction(mockAvd1, Runnable {}, "pause", null)
- )
- val semanticActions2 = MediaButton(
- playOrPause = MediaAction(mockAvd2, Runnable {}, "loading", null)
- )
+ val semanticActions0 =
+ MediaButton(playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null))
+ val semanticActions1 =
+ MediaButton(playOrPause = MediaAction(mockAvd1, Runnable {}, "pause", null))
+ val semanticActions2 =
+ MediaButton(playOrPause = MediaAction(mockAvd2, Runnable {}, "loading", null))
val state0 = mediaData.copy(semanticActions = semanticActions0)
val state1 = mediaData.copy(semanticActions = semanticActions1)
val state2 = mediaData.copy(semanticActions = semanticActions2)
@@ -1089,11 +1109,10 @@ public class MediaControlPanelTest : SysuiTestCase() {
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)
+ 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)
@@ -1432,9 +1451,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun actionPlayPauseClick_isLogged() {
- val semanticActions = MediaButton(
- playOrPause = MediaAction(null, Runnable {}, "play", null)
- )
+ val semanticActions =
+ MediaButton(playOrPause = MediaAction(null, Runnable {}, "play", null))
val data = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -1446,9 +1464,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun actionPrevClick_isLogged() {
- val semanticActions = MediaButton(
- prevOrCustom = MediaAction(null, Runnable {}, "previous", null)
- )
+ val semanticActions =
+ MediaButton(prevOrCustom = MediaAction(null, Runnable {}, "previous", null))
val data = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -1460,9 +1477,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun actionNextClick_isLogged() {
- val semanticActions = MediaButton(
- nextOrCustom = MediaAction(null, Runnable {}, "next", null)
- )
+ val semanticActions =
+ MediaButton(nextOrCustom = MediaAction(null, Runnable {}, "next", null))
val data = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -1474,9 +1490,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun actionCustom0Click_isLogged() {
- val semanticActions = MediaButton(
- custom0 = MediaAction(null, Runnable {}, "custom 0", null)
- )
+ val semanticActions =
+ MediaButton(custom0 = MediaAction(null, Runnable {}, "custom 0", null))
val data = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -1488,9 +1503,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun actionCustom1Click_isLogged() {
- val semanticActions = MediaButton(
- custom1 = MediaAction(null, Runnable {}, "custom 1", null)
- )
+ val semanticActions =
+ MediaButton(custom1 = MediaAction(null, Runnable {}, "custom 1", null))
val data = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
@@ -1502,13 +1516,14 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun actionCustom2Click_isLogged() {
- val actions = listOf(
- MediaAction(null, Runnable {}, "action 0", null),
- MediaAction(null, Runnable {}, "action 1", null),
- MediaAction(null, Runnable {}, "action 2", null),
- MediaAction(null, Runnable {}, "action 3", null),
- MediaAction(null, Runnable {}, "action 4", null)
- )
+ val actions =
+ listOf(
+ MediaAction(null, Runnable {}, "action 0", null),
+ MediaAction(null, Runnable {}, "action 1", null),
+ MediaAction(null, Runnable {}, "action 2", null),
+ MediaAction(null, Runnable {}, "action 3", null),
+ MediaAction(null, Runnable {}, "action 4", null)
+ )
val data = mediaData.copy(actions = actions)
player.attachPlayer(viewHolder)
@@ -1520,13 +1535,14 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun actionCustom3Click_isLogged() {
- val actions = listOf(
- MediaAction(null, Runnable {}, "action 0", null),
- MediaAction(null, Runnable {}, "action 1", null),
- MediaAction(null, Runnable {}, "action 2", null),
- MediaAction(null, Runnable {}, "action 3", null),
- MediaAction(null, Runnable {}, "action 4", null)
- )
+ val actions =
+ listOf(
+ MediaAction(null, Runnable {}, "action 0", null),
+ MediaAction(null, Runnable {}, "action 1", null),
+ MediaAction(null, Runnable {}, "action 2", null),
+ MediaAction(null, Runnable {}, "action 3", null),
+ MediaAction(null, Runnable {}, "action 4", null)
+ )
val data = mediaData.copy(actions = actions)
player.attachPlayer(viewHolder)
@@ -1538,13 +1554,14 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun actionCustom4Click_isLogged() {
- val actions = listOf(
- MediaAction(null, Runnable {}, "action 0", null),
- MediaAction(null, Runnable {}, "action 1", null),
- MediaAction(null, Runnable {}, "action 2", null),
- MediaAction(null, Runnable {}, "action 3", null),
- MediaAction(null, Runnable {}, "action 4", null)
- )
+ val actions =
+ listOf(
+ MediaAction(null, Runnable {}, "action 0", null),
+ MediaAction(null, Runnable {}, "action 1", null),
+ MediaAction(null, Runnable {}, "action 2", null),
+ MediaAction(null, Runnable {}, "action 3", null),
+ MediaAction(null, Runnable {}, "action 4", null)
+ )
val data = mediaData.copy(actions = actions)
player.attachPlayer(viewHolder)
@@ -1608,8 +1625,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
// THEN it shows without dismissing keyguard first
captor.value.onClick(viewHolder.player)
- verify(activityStarter).startActivity(eq(clickIntent), eq(true),
- nullable(), eq(true))
+ verify(activityStarter).startActivity(eq(clickIntent), eq(true), nullable(), eq(true))
}
@Test
@@ -1697,20 +1713,22 @@ public class MediaControlPanelTest : SysuiTestCase() {
fun bindRecommendation_listHasTooFewRecs_notDisplayed() {
player.attachRecommendation(recommendationViewHolder)
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
- val data = smartspaceData.copy(
- recommendations = listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("subtitle1")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle2")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "title1")
+ .setSubtitle("subtitle1")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("subtitle2")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ )
)
- )
player.bindRecommendation(data)
@@ -1722,30 +1740,32 @@ public class MediaControlPanelTest : SysuiTestCase() {
fun bindRecommendation_listHasTooFewRecsWithIcons_notDisplayed() {
player.attachRecommendation(recommendationViewHolder)
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
- val data = smartspaceData.copy(
- recommendations = listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("subtitle1")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle2")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "empty icon 1")
- .setSubtitle("subtitle2")
- .setIcon(null)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "empty icon 2")
- .setSubtitle("subtitle2")
- .setIcon(null)
- .setExtras(Bundle.EMPTY)
- .build(),
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "title1")
+ .setSubtitle("subtitle1")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("subtitle2")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "empty icon 1")
+ .setSubtitle("subtitle2")
+ .setIcon(null)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "empty icon 2")
+ .setSubtitle("subtitle2")
+ .setIcon(null)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ )
)
- )
player.bindRecommendation(data)
@@ -1765,25 +1785,27 @@ public class MediaControlPanelTest : SysuiTestCase() {
val subtitle3 = "Subtitle3"
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
- val data = smartspaceData.copy(
- recommendations = listOf(
- SmartspaceAction.Builder("id1", title1)
- .setSubtitle(subtitle1)
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", title2)
- .setSubtitle(subtitle2)
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", title3)
- .setSubtitle(subtitle3)
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build()
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", title1)
+ .setSubtitle(subtitle1)
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", title2)
+ .setSubtitle(subtitle2)
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", title3)
+ .setSubtitle(subtitle3)
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
)
- )
player.bindRecommendation(data)
assertThat(recTitle1.text).isEqualTo(title1)
@@ -1798,15 +1820,17 @@ public class MediaControlPanelTest : SysuiTestCase() {
fun bindRecommendation_noTitle_subtitleNotShown() {
player.attachRecommendation(recommendationViewHolder)
- val data = smartspaceData.copy(
- recommendations = listOf(
- SmartspaceAction.Builder("id1", "")
- .setSubtitle("fake subtitle")
- .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
- .setExtras(Bundle.EMPTY)
- .build()
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "")
+ .setSubtitle("fake subtitle")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
)
- )
player.bindRecommendation(data)
assertThat(recSubtitle1.text).isEqualTo("")
@@ -1818,25 +1842,27 @@ public class MediaControlPanelTest : SysuiTestCase() {
player.attachRecommendation(recommendationViewHolder)
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
- val data = smartspaceData.copy(
- recommendations = listOf(
- SmartspaceAction.Builder("id1", "")
- .setSubtitle("fake subtitle")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("fake subtitle")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "")
- .setSubtitle("fake subtitle")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build()
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "")
+ .setSubtitle("fake subtitle")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("fake subtitle")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "")
+ .setSubtitle("fake subtitle")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
)
- )
player.bindRecommendation(data)
assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
@@ -1850,25 +1876,27 @@ public class MediaControlPanelTest : SysuiTestCase() {
player.attachRecommendation(recommendationViewHolder)
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
- val data = smartspaceData.copy(
- recommendations = listOf(
- SmartspaceAction.Builder("id1", "")
- .setSubtitle("")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("subtitle3")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build()
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "")
+ .setSubtitle("")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "title3")
+ .setSubtitle("subtitle3")
+ .setIcon(icon)
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
)
- )
player.bindRecommendation(data)
assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
@@ -1880,25 +1908,27 @@ public class MediaControlPanelTest : SysuiTestCase() {
fun bindRecommendation_noneHaveSubtitles_subtitleViewsGone() {
useRealConstraintSets()
player.attachRecommendation(recommendationViewHolder)
- val data = smartspaceData.copy(
- recommendations = listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("")
- .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("")
- .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("")
- .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
- .setExtras(Bundle.EMPTY)
- .build()
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "title1")
+ .setSubtitle("")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "title3")
+ .setSubtitle("")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
)
- )
player.bindRecommendation(data)
@@ -1911,25 +1941,27 @@ public class MediaControlPanelTest : SysuiTestCase() {
fun bindRecommendation_noneHaveTitles_titleAndSubtitleViewsGone() {
useRealConstraintSets()
player.attachRecommendation(recommendationViewHolder)
- val data = smartspaceData.copy(
- recommendations = listOf(
- SmartspaceAction.Builder("id1", "")
- .setSubtitle("subtitle1")
- .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "")
- .setSubtitle("subtitle2")
- .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "")
- .setSubtitle("subtitle3")
- .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
- .setExtras(Bundle.EMPTY)
- .build()
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "")
+ .setSubtitle("subtitle1")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "")
+ .setSubtitle("subtitle2")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "")
+ .setSubtitle("subtitle3")
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
)
- )
player.bindRecommendation(data)
@@ -1942,20 +1974,23 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
- withArgCaptor { verify(seekBarViewModel).setScrubbingChangeListener(capture()) }
+ withArgCaptor {
+ verify(seekBarViewModel).setScrubbingChangeListener(capture())
+ }
- private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener =
- withArgCaptor { verify(seekBarViewModel).setEnabledChangeListener(capture()) }
+ private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = withArgCaptor {
+ verify(seekBarViewModel).setEnabledChangeListener(capture())
+ }
/**
- * Update our test to use real ConstraintSets instead of mocks.
+ * Update our test to use real ConstraintSets instead of mocks.
*
- * Some item visibilities, such as the seekbar visibility, are dependent on other action's
- * visibilities. If we use mocks for the ConstraintSets, then action visibility changes are
- * just thrown away instead of being saved for reference later. This method sets us up to use
- * ConstraintSets so that we do save visibility changes.
+ * Some item visibilities, such as the seekbar visibility, are dependent on other action's
+ * visibilities. If we use mocks for the ConstraintSets, then action visibility changes are just
+ * thrown away instead of being saved for reference later. This method sets us up to use
+ * ConstraintSets so that we do save visibility changes.
*
- * TODO(b/229740380): Can/should we use real expanded and collapsed sets for all tests?
+ * TODO(b/229740380): Can/should we use real expanded and collapsed sets for all tests?
*/
private fun useRealConstraintSets() {
expandedSet = ConstraintSet()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 954b4386b71d..071604dc5790 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.graphics.Rect
import android.provider.Settings
@@ -84,10 +84,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
private lateinit var statusBarCallback: ArgumentCaptor<(StatusBarStateController.StateListener)>
@Captor
private lateinit var dreamOverlayCallback:
- ArgumentCaptor<(DreamOverlayStateController.Callback)>
- @JvmField
- @Rule
- val mockito = MockitoJUnit.rule()
+ ArgumentCaptor<(DreamOverlayStateController.Callback)>
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
@@ -98,13 +96,15 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
@Before
fun setup() {
- context.getOrCreateTestableResources().addOverride(
- R.bool.config_use_split_notification_shade, false)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_split_notification_shade, false)
mediaFrame = FrameLayout(context)
testableLooper = TestableLooper.get(this)
fakeHandler = FakeHandler(testableLooper.looper)
whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
- mediaHierarchyManager = MediaHierarchyManager(
+ mediaHierarchyManager =
+ MediaHierarchyManager(
context,
statusBarStateController,
keyguardStateController,
@@ -116,7 +116,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
wakefulnessLifecycle,
notifPanelEvents,
settings,
- fakeHandler,)
+ fakeHandler,
+ )
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())
verify(dreamOverlayStateController).addCallback(dreamOverlayCallback.capture())
@@ -125,7 +126,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP)
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
whenever(mediaCarouselController.mediaCarouselScrollHandler)
- .thenReturn(mediaCarouselScrollHandler)
+ .thenReturn(mediaCarouselScrollHandler)
val observer = wakefullnessObserver.value
assertNotNull("lifecycle observer wasn't registered", observer)
observer.onFinishedWakingUp()
@@ -151,30 +152,53 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
fun testBlockedWhenScreenTurningOff() {
// Let's set it onto QS:
mediaHierarchyManager.qsExpansion = 1.0f
- verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
- any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ ArgumentMatchers.anyInt(),
+ any(MediaHostState::class.java),
+ anyBoolean(),
+ anyLong(),
+ anyLong()
+ )
val observer = wakefullnessObserver.value
assertNotNull("lifecycle observer wasn't registered", observer)
observer.onStartedGoingToSleep()
clearInvocations(mediaCarouselController)
mediaHierarchyManager.qsExpansion = 0.0f
verify(mediaCarouselController, times(0))
- .onDesiredLocationChanged(ArgumentMatchers.anyInt(),
- any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
+ .onDesiredLocationChanged(
+ ArgumentMatchers.anyInt(),
+ any(MediaHostState::class.java),
+ anyBoolean(),
+ anyLong(),
+ anyLong()
+ )
}
@Test
fun testAllowedWhenNotTurningOff() {
// Let's set it onto QS:
mediaHierarchyManager.qsExpansion = 1.0f
- verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
- any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ ArgumentMatchers.anyInt(),
+ any(MediaHostState::class.java),
+ anyBoolean(),
+ anyLong(),
+ anyLong()
+ )
val observer = wakefullnessObserver.value
assertNotNull("lifecycle observer wasn't registered", observer)
clearInvocations(mediaCarouselController)
mediaHierarchyManager.qsExpansion = 0.0f
- verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
- any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ ArgumentMatchers.anyInt(),
+ any(MediaHostState::class.java),
+ anyBoolean(),
+ anyLong(),
+ anyLong()
+ )
}
@Test
@@ -183,22 +207,26 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
// Let's transition all the way to full shade
mediaHierarchyManager.setTransitionToFullShadeAmount(100000f)
- verify(mediaCarouselController).onDesiredLocationChanged(
- eq(MediaHierarchyManager.LOCATION_QQS),
- any(MediaHostState::class.java),
- eq(false),
- anyLong(),
- anyLong())
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_QQS),
+ any(MediaHostState::class.java),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
clearInvocations(mediaCarouselController)
// Let's go back to the lock screen
mediaHierarchyManager.setTransitionToFullShadeAmount(0.0f)
- verify(mediaCarouselController).onDesiredLocationChanged(
- eq(MediaHierarchyManager.LOCATION_LOCKSCREEN),
- any(MediaHostState::class.java),
- eq(false),
- anyLong(),
- anyLong())
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_LOCKSCREEN),
+ any(MediaHostState::class.java),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
// Let's make sure alpha is set
mediaHierarchyManager.setTransitionToFullShadeAmount(2.0f)
@@ -302,7 +330,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
val expectedTranslation = LOCKSCREEN_TOP - QS_TOP
assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY())
- .isEqualTo(expectedTranslation)
+ .isEqualTo(expectedTranslation)
}
@Test
@@ -343,27 +371,31 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
fun testDream() {
goToDream()
setMediaDreamComplicationEnabled(true)
- verify(mediaCarouselController).onDesiredLocationChanged(
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_DREAM_OVERLAY),
nullable(),
eq(false),
anyLong(),
- anyLong())
+ anyLong()
+ )
clearInvocations(mediaCarouselController)
setMediaDreamComplicationEnabled(false)
- verify(mediaCarouselController).onDesiredLocationChanged(
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_QQS),
any(MediaHostState::class.java),
eq(false),
anyLong(),
- anyLong())
+ anyLong()
+ )
}
private fun enableSplitShade() {
- context.getOrCreateTestableResources().addOverride(
- R.bool.config_use_split_notification_shade, true
- )
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_split_notification_shade, true)
configurationController.notifyConfigurationChanged()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt
index 6e38d26411ee..32b822d798f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.MediaTestUtils
+import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -33,13 +35,10 @@ import org.mockito.junit.MockitoJUnit
@RunWith(AndroidTestingRunner::class)
public class MediaPlayerDataTest : SysuiTestCase() {
- @Mock
- private lateinit var playerIsPlaying: MediaControlPanel
+ @Mock private lateinit var playerIsPlaying: MediaControlPanel
private var systemClock: FakeSystemClock = FakeSystemClock()
- @JvmField
- @Rule
- val mockito = MockitoJUnit.rule()
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
companion object {
val LOCAL = MediaData.PLAYBACK_LOCAL
@@ -61,10 +60,20 @@ public class MediaPlayerDataTest : SysuiTestCase() {
val playerIsRemote = mock(MediaControlPanel::class.java)
val dataIsRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION)
- MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote, systemClock,
- isSsReactivated = false)
- MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock,
- isSsReactivated = false)
+ MediaPlayerData.addMediaPlayer(
+ "2",
+ dataIsRemote,
+ playerIsRemote,
+ systemClock,
+ isSsReactivated = false
+ )
+ MediaPlayerData.addMediaPlayer(
+ "1",
+ dataIsPlaying,
+ playerIsPlaying,
+ systemClock,
+ isSsReactivated = false
+ )
val players = MediaPlayerData.players()
assertThat(players).hasSize(2)
@@ -79,22 +88,42 @@ public class MediaPlayerDataTest : SysuiTestCase() {
val playerIsPlaying2 = mock(MediaControlPanel::class.java)
var dataIsPlaying2 = createMediaData("app2", !PLAYING, LOCAL, !RESUMPTION)
- MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock,
- isSsReactivated = false)
+ MediaPlayerData.addMediaPlayer(
+ "1",
+ dataIsPlaying1,
+ playerIsPlaying1,
+ systemClock,
+ isSsReactivated = false
+ )
systemClock.advanceTime(1)
- MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock,
- isSsReactivated = false)
+ MediaPlayerData.addMediaPlayer(
+ "2",
+ dataIsPlaying2,
+ playerIsPlaying2,
+ systemClock,
+ isSsReactivated = false
+ )
systemClock.advanceTime(1)
dataIsPlaying1 = createMediaData("app1", !PLAYING, LOCAL, !RESUMPTION)
dataIsPlaying2 = createMediaData("app2", PLAYING, LOCAL, !RESUMPTION)
- MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock,
- isSsReactivated = false)
+ MediaPlayerData.addMediaPlayer(
+ "1",
+ dataIsPlaying1,
+ playerIsPlaying1,
+ systemClock,
+ isSsReactivated = false
+ )
systemClock.advanceTime(1)
- MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock,
- isSsReactivated = false)
+ MediaPlayerData.addMediaPlayer(
+ "2",
+ dataIsPlaying2,
+ playerIsPlaying2,
+ systemClock,
+ isSsReactivated = false
+ )
systemClock.advanceTime(1)
val players = MediaPlayerData.players()
@@ -122,26 +151,60 @@ public class MediaPlayerDataTest : SysuiTestCase() {
val dataUndetermined = createMediaData("app6", UNDETERMINED, LOCAL, RESUMPTION)
MediaPlayerData.addMediaPlayer(
- "3", dataIsStoppedAndLocal, playerIsStoppedAndLocal, systemClock,
- isSsReactivated = false)
+ "3",
+ dataIsStoppedAndLocal,
+ playerIsStoppedAndLocal,
+ systemClock,
+ isSsReactivated = false
+ )
MediaPlayerData.addMediaPlayer(
- "5", dataIsStoppedAndRemote, playerIsStoppedAndRemote, systemClock,
- isSsReactivated = false)
- MediaPlayerData.addMediaPlayer("4", dataCanResume, playerCanResume, systemClock,
- isSsReactivated = false)
- MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock,
- isSsReactivated = false)
+ "5",
+ dataIsStoppedAndRemote,
+ playerIsStoppedAndRemote,
+ systemClock,
+ isSsReactivated = false
+ )
MediaPlayerData.addMediaPlayer(
- "2", dataIsPlayingAndRemote, playerIsPlayingAndRemote, systemClock,
- isSsReactivated = false)
- MediaPlayerData.addMediaPlayer("6", dataUndetermined, playerUndetermined, systemClock,
- isSsReactivated = false)
+ "4",
+ dataCanResume,
+ playerCanResume,
+ systemClock,
+ isSsReactivated = false
+ )
+ MediaPlayerData.addMediaPlayer(
+ "1",
+ dataIsPlaying,
+ playerIsPlaying,
+ systemClock,
+ isSsReactivated = false
+ )
+ MediaPlayerData.addMediaPlayer(
+ "2",
+ dataIsPlayingAndRemote,
+ playerIsPlayingAndRemote,
+ systemClock,
+ isSsReactivated = false
+ )
+ MediaPlayerData.addMediaPlayer(
+ "6",
+ dataUndetermined,
+ playerUndetermined,
+ systemClock,
+ isSsReactivated = false
+ )
val players = MediaPlayerData.players()
assertThat(players).hasSize(6)
- assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote,
- playerIsStoppedAndRemote, playerIsStoppedAndLocal, playerUndetermined,
- playerCanResume).inOrder()
+ assertThat(players)
+ .containsExactly(
+ playerIsPlaying,
+ playerIsPlayingAndRemote,
+ playerIsStoppedAndRemote,
+ playerIsStoppedAndLocal,
+ playerUndetermined,
+ playerCanResume
+ )
+ .inOrder()
}
@Test
@@ -153,13 +216,23 @@ public class MediaPlayerDataTest : SysuiTestCase() {
assertThat(MediaPlayerData.players()).hasSize(0)
- MediaPlayerData.addMediaPlayer(keyA, data, playerIsPlaying, systemClock,
- isSsReactivated = false)
+ MediaPlayerData.addMediaPlayer(
+ keyA,
+ data,
+ playerIsPlaying,
+ systemClock,
+ isSsReactivated = false
+ )
systemClock.advanceTime(1)
assertThat(MediaPlayerData.players()).hasSize(1)
- MediaPlayerData.addMediaPlayer(keyB, data, playerIsPlaying, systemClock,
- isSsReactivated = false)
+ MediaPlayerData.addMediaPlayer(
+ keyB,
+ data,
+ playerIsPlaying,
+ systemClock,
+ isSsReactivated = false
+ )
systemClock.advanceTime(1)
assertThat(MediaPlayerData.players()).hasSize(2)
@@ -177,12 +250,13 @@ public class MediaPlayerDataTest : SysuiTestCase() {
isPlaying: Boolean?,
location: Int,
resumption: Boolean
- ) = MediaTestUtils.emptyMediaData.copy(
- app = app,
- packageName = "package: $app",
- playbackLocation = location,
- resumption = resumption,
- notificationKey = "key: $app",
- isPlaying = isPlaying
- )
+ ) =
+ MediaTestUtils.emptyMediaData.copy(
+ app = app,
+ packageName = "package: $app",
+ playbackLocation = location,
+ resumption = resumption,
+ notificationKey = "key: $app",
+ isPlaying = isPlaying
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 622a512720d9..6b7615557d83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -22,13 +22,13 @@ import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
-import com.android.systemui.media.MediaCarouselController.Companion.CONTROLS_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.DETAILS_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.DURATION
-import com.android.systemui.media.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.MEDIATITLES_DELAY
-import com.android.systemui.media.MediaCarouselController.Companion.TRANSFORM_BEZIER
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.ANIMATION_BASE_DURATION
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.CONTROLS_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DETAILS_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIATITLES_DELAY
+import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.TRANSFORM_BEZIER
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionViewState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt
index 311aa9649911..323b7818ed3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.media
+package com.android.systemui.media.controls.ui
-import org.mockito.Mockito.`when` as whenever
import android.animation.Animator
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
@@ -29,10 +28,11 @@ 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.times
import org.mockito.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
@SmallTest
@@ -55,8 +55,7 @@ class MetadataAnimationHandlerTest : SysuiTestCase() {
handler = MetadataAnimationHandler(exitAnimator, enterAnimator)
}
- @After
- fun tearDown() {}
+ @After fun tearDown() {}
@Test
fun firstBind_startsAnimationSet() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt
index d087b0fe4413..d6cff81c0aaa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.media
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.controls.ui
import android.graphics.Canvas
import android.graphics.Color
@@ -107,7 +123,6 @@ class SquigglyProgressTest : SysuiTestCase() {
val (wavePaint, linePaint) = paintCaptor.getAllValues()
assertThat(wavePaint.color).isEqualTo(tint)
- assertThat(linePaint.color).isEqualTo(
- ColorUtils.setAlphaComponent(tint, DISABLED_ALPHA))
+ assertThat(linePaint.color).isEqualTo(ColorUtils.setAlphaComponent(tint, DISABLED_ALPHA))
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
index 29188da46562..ce885c0bba2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
@@ -25,7 +25,7 @@ import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.util.animation.UniqueObjectHostView;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index af530163e289..ed928a35a20e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -33,8 +33,8 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.DreamMediaEntryComplication;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaData;
-import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.controls.models.player.MediaData;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
index 1078cdaa57c4..e009e8651f2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
@@ -19,9 +19,9 @@ package com.android.systemui.media.taptotransfer.common
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 7c83cb74bb77..6a4c0f60466d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -22,6 +22,9 @@ import android.graphics.drawable.Drawable
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -62,6 +65,34 @@ class MediaTttUtilsTest : SysuiTestCase() {
}
@Test
+ fun getIconFromPackageName_nullPackageName_returnsDefault() {
+ val icon = MediaTttUtils.getIconFromPackageName(context, appPackageName = null, logger)
+
+ val expectedDesc =
+ ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name)
+ .loadContentDescription(context)
+ assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc)
+ }
+
+ @Test
+ fun getIconFromPackageName_invalidPackageName_returnsDefault() {
+ val icon = MediaTttUtils.getIconFromPackageName(context, "fakePackageName", logger)
+
+ val expectedDesc =
+ ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name)
+ .loadContentDescription(context)
+ assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc)
+ }
+
+ @Test
+ fun getIconFromPackageName_validPackageName_returnsAppInfo() {
+ val icon = MediaTttUtils.getIconFromPackageName(context, PACKAGE_NAME, logger)
+
+ assertThat(icon)
+ .isEqualTo(Icon.Loaded(appIconFromPackageName, ContentDescription.Loaded(APP_NAME)))
+ }
+
+ @Test
fun getIconInfoFromPackageName_nullPackageName_returnsDefault() {
val iconInfo =
MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 9577274eef8d..8c3ae3d01f1d 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
@@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -49,6 +50,7 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -70,6 +72,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
@Mock
private lateinit var configurationController: ConfigurationController
@Mock
+ private lateinit var mediaTttFlags: MediaTttFlags
+ @Mock
private lateinit var powerManager: PowerManager
@Mock
private lateinit var viewUtil: ViewUtil
@@ -85,6 +89,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
@@ -107,6 +112,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
configurationController,
powerManager,
Handler.getMain(),
+ mediaTttFlags,
receiverUiEventLogger,
viewUtil,
)
@@ -118,6 +124,30 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
}
@Test
+ fun commandQueueCallback_flagOff_noCallbackAdded() {
+ reset(commandQueue)
+ whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false)
+
+ controllerReceiver = MediaTttChipControllerReceiver(
+ commandQueue,
+ context,
+ logger,
+ windowManager,
+ FakeExecutor(FakeSystemClock()),
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ Handler.getMain(),
+ mediaTttFlags,
+ receiverUiEventLogger,
+ viewUtil,
+ )
+ controllerReceiver.start()
+
+ verify(commandQueue, never()).addCallback(any())
+ }
+
+ @Test
fun commandQueueCallback_closeToSender_triggersChip() {
val appName = "FakeAppName"
commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
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
deleted file mode 100644
index 3a8a51d42f77..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ /dev/null
@@ -1,868 +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.media.taptotransfer.sender
-
-import android.app.StatusBarManager
-import android.content.Context
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
-import android.graphics.drawable.Drawable
-import android.media.MediaRoute2Info
-import android.os.PowerManager
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowManager
-import android.view.accessibility.AccessibilityManager
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.statusbar.IUndoMediaTransferCallback
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.util.view.ViewUtil
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-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.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class MediaTttChipControllerSenderTest : SysuiTestCase() {
- private lateinit var controllerSender: TestMediaTttChipControllerSender
-
- @Mock
- private lateinit var packageManager: PackageManager
- @Mock
- private lateinit var applicationInfo: ApplicationInfo
- @Mock
- private lateinit var logger: MediaTttLogger
- @Mock
- private lateinit var accessibilityManager: AccessibilityManager
- @Mock
- private lateinit var configurationController: ConfigurationController
- @Mock
- private lateinit var powerManager: PowerManager
- @Mock
- private lateinit var windowManager: WindowManager
- @Mock
- private lateinit var commandQueue: CommandQueue
- @Mock
- private lateinit var falsingManager: FalsingManager
- @Mock
- private lateinit var falsingCollector: FalsingCollector
- @Mock
- private lateinit var viewUtil: ViewUtil
- private lateinit var commandQueueCallback: CommandQueue.Callbacks
- private lateinit var fakeAppIconDrawable: Drawable
- private lateinit var fakeClock: FakeSystemClock
- private lateinit var fakeExecutor: FakeExecutor
- private lateinit var uiEventLoggerFake: UiEventLoggerFake
- private lateinit var senderUiEventLogger: MediaTttSenderUiEventLogger
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
- whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
- whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
- whenever(packageManager.getApplicationInfo(
- eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>()
- )).thenReturn(applicationInfo)
- context.setMockPackageManager(packageManager)
-
- fakeClock = FakeSystemClock()
- fakeExecutor = FakeExecutor(fakeClock)
-
- uiEventLoggerFake = UiEventLoggerFake()
- senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
-
- whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
-
- controllerSender = TestMediaTttChipControllerSender(
- commandQueue,
- context,
- logger,
- windowManager,
- fakeExecutor,
- accessibilityManager,
- configurationController,
- powerManager,
- senderUiEventLogger,
- falsingManager,
- falsingCollector,
- viewUtil,
- )
- controllerSender.start()
-
- val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
- verify(commandQueue).addCallback(callbackCaptor.capture())
- commandQueueCallback = callbackCaptor.value!!
- }
-
- @Test
- fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- almostCloseToStartCast().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id
- )
- }
-
- @Test
- fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- almostCloseToEndCast().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id
- )
- }
-
- @Test
- fun commandQueueCallback_transferToReceiverTriggered_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id
- )
- }
-
- @Test
- fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id
- )
- }
-
- @Test
- fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToReceiverSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id
- )
- }
-
- @Test
- fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToThisDeviceSucceeded().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id
- )
- }
-
- @Test
- fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToReceiverFailed().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id
- )
- }
-
- @Test
- fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
- routeInfo,
- null
- )
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToThisDeviceFailed().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id
- )
- }
-
- @Test
- fun commandQueueCallback_farFromReceiver_noChipShown() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
- routeInfo,
- null
- )
-
- verify(windowManager, never()).addView(any(), any())
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER.id
- )
- }
-
- @Test
- fun commandQueueCallback_almostCloseThenFarFromReceiver_chipShownThenHidden() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
- routeInfo,
- null
- )
-
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
- routeInfo,
- null
- )
-
- val viewCaptor = ArgumentCaptor.forClass(View::class.java)
- verify(windowManager).addView(viewCaptor.capture(), any())
- verify(windowManager).removeView(viewCaptor.value)
- }
-
- @Test
- fun commandQueueCallback_invalidStateParam_noChipShown() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- 100,
- routeInfo,
- null
- )
-
- verify(windowManager, never()).addView(any(), any())
- }
-
- @Test
- fun receivesNewStateFromCommandQueue_isLogged() {
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
- routeInfo,
- null
- )
-
- verify(logger).logStateChange(any(), any(), any())
- }
-
- @Test
- fun almostCloseToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
- val state = almostCloseToStartCast()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun almostCloseToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
- val state = almostCloseToEndCast()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToReceiverTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
- val state = transferToReceiverTriggered()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToThisDeviceTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
- val state = transferToThisDeviceTriggered()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToReceiverSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
- val state = transferToReceiverSucceeded()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() {
- controllerSender.displayView(transferToReceiverSucceeded(undoCallback = null))
-
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
-
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
- }
-
- @Test
- fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
- var undoCallbackCalled = false
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {
- undoCallbackCalled = true
- }
- }
-
- controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
- getChipView().getUndoButton().performClick()
-
- assertThat(undoCallbackCalled).isTrue()
- }
-
- @Test
- fun transferToReceiverSucceeded_withUndoRunnable_falseTap_callbackNotRun() {
- whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
- var undoCallbackCalled = false
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {
- undoCallbackCalled = true
- }
- }
-
- controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
- getChipView().getUndoButton().performClick()
-
- assertThat(undoCallbackCalled).isFalse()
- }
-
- @Test
- fun transferToReceiverSucceeded_withUndoRunnable_realTap_callbackRun() {
- whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
- var undoCallbackCalled = false
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {
- undoCallbackCalled = true
- }
- }
-
- controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
- getChipView().getUndoButton().performClick()
-
- assertThat(undoCallbackCalled).isTrue()
- }
-
- @Test
- fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- controllerSender.displayView(transferToReceiverSucceeded(undoCallback))
-
- getChipView().getUndoButton().performClick()
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToThisDeviceTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id
- )
- }
-
- @Test
- fun transferToThisDeviceSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
- val state = transferToThisDeviceSucceeded()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(chipView.getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToThisDeviceSucceeded_nullUndoRunnable_noUndo() {
- controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback = null))
-
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback))
-
- val chipView = getChipView()
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
- assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
- }
-
- @Test
- fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
- var undoCallbackCalled = false
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {
- undoCallbackCalled = true
- }
- }
-
- controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback))
- getChipView().getUndoButton().performClick()
-
- assertThat(undoCallbackCalled).isTrue()
- }
-
- @Test
- fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() {
- val undoCallback = object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- controllerSender.displayView(transferToThisDeviceSucceeded(undoCallback))
-
- getChipView().getUndoButton().performClick()
-
- assertThat(getChipView().getChipText()).isEqualTo(
- transferToReceiverTriggered().state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
- MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
- )
- }
-
- @Test
- fun transferToReceiverFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
- val state = transferToReceiverFailed()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(getChipView().getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun transferToThisDeviceFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
- val state = transferToThisDeviceFailed()
- controllerSender.displayView(state)
-
- val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
- assertThat(getChipView().getChipText()).isEqualTo(
- state.state.getChipTextString(context, OTHER_DEVICE_NAME)
- )
- assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
- assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun changeFromAlmostCloseToStartToTransferTriggered_loadingIconAppears() {
- controllerSender.displayView(almostCloseToStartCast())
- controllerSender.displayView(transferToReceiverTriggered())
-
- assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun changeFromTransferTriggeredToTransferSucceeded_loadingIconDisappears() {
- controllerSender.displayView(transferToReceiverTriggered())
- controllerSender.displayView(transferToReceiverSucceeded())
-
- assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
- }
-
- @Test
- fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() {
- controllerSender.displayView(transferToReceiverTriggered())
- controllerSender.displayView(
- transferToReceiverSucceeded(
- object : IUndoMediaTransferCallback.Stub() {
- override fun onUndoTriggered() {}
- }
- )
- )
-
- assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun changeFromTransferSucceededToAlmostCloseToStart_undoButtonDisappears() {
- controllerSender.displayView(transferToReceiverSucceeded())
- controllerSender.displayView(almostCloseToStartCast())
-
- assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
- }
-
- @Test
- fun changeFromTransferTriggeredToTransferFailed_failureIconAppears() {
- controllerSender.displayView(transferToReceiverTriggered())
- controllerSender.displayView(transferToReceiverFailed())
-
- assertThat(getChipView().getFailureIcon().visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun transferToReceiverTriggeredThenRemoveView_viewStillDisplayed() {
- controllerSender.displayView(transferToReceiverTriggered())
- fakeClock.advanceTime(1000L)
-
- controllerSender.removeView("fakeRemovalReason")
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayed() {
- controllerSender.displayView(transferToReceiverTriggered())
-
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
- routeInfo,
- null
- )
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToReceiverTriggeredThenRemoveView_eventuallyTimesOut() {
- controllerSender.displayView(transferToReceiverTriggered())
-
- controllerSender.removeView("fakeRemovalReason")
- fakeClock.advanceTime(TIMEOUT + 1L)
-
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun transferToThisDeviceTriggeredThenRemoveView_viewStillDisplayed() {
- controllerSender.displayView(transferToThisDeviceTriggered())
- fakeClock.advanceTime(1000L)
-
- controllerSender.removeView("fakeRemovalReason")
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToThisDeviceTriggeredThenRemoveView_eventuallyTimesOut() {
- controllerSender.displayView(transferToThisDeviceTriggered())
-
- controllerSender.removeView("fakeRemovalReason")
- fakeClock.advanceTime(TIMEOUT + 1L)
-
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayed() {
- controllerSender.displayView(transferToThisDeviceTriggered())
-
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
- routeInfo,
- null
- )
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToReceiverSucceededThenRemoveView_viewStillDisplayed() {
- controllerSender.displayView(transferToReceiverSucceeded())
-
- controllerSender.removeView("fakeRemovalReason")
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToReceiverSucceededThenRemoveView_eventuallyTimesOut() {
- controllerSender.displayView(transferToReceiverSucceeded())
-
- controllerSender.removeView("fakeRemovalReason")
- fakeClock.advanceTime(TIMEOUT + 1L)
-
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayed() {
- controllerSender.displayView(transferToReceiverSucceeded())
-
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
- routeInfo,
- null
- )
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToThisDeviceSucceededThenRemoveView_viewStillDisplayed() {
- controllerSender.displayView(transferToThisDeviceSucceeded())
-
- controllerSender.removeView("fakeRemovalReason")
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- @Test
- fun transferToThisDeviceSucceededThenRemoveView_eventuallyTimesOut() {
- controllerSender.displayView(transferToThisDeviceSucceeded())
-
- controllerSender.removeView("fakeRemovalReason")
- fakeClock.advanceTime(TIMEOUT + 1L)
-
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayed() {
- controllerSender.displayView(transferToThisDeviceSucceeded())
-
- commandQueueCallback.updateMediaTapToTransferSenderDisplay(
- StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
- routeInfo,
- null
- )
- fakeExecutor.runAllReady()
-
- verify(windowManager, never()).removeView(any())
- verify(logger).logRemovalBypass(any(), any())
- }
-
- private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
-
- private fun ViewGroup.getChipText(): String =
- (this.requireViewById<TextView>(R.id.text)).text as String
-
- private fun ViewGroup.getLoadingIconVisibility(): Int =
- this.requireViewById<View>(R.id.loading).visibility
-
- private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.undo)
-
- private fun ViewGroup.getFailureIcon(): View = this.requireViewById(R.id.failure_icon)
-
- private fun getChipView(): ViewGroup {
- val viewCaptor = ArgumentCaptor.forClass(View::class.java)
- verify(windowManager).addView(viewCaptor.capture(), any())
- return viewCaptor.value as ViewGroup
- }
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun almostCloseToStartCast() =
- ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun almostCloseToEndCast() =
- ChipSenderInfo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverTriggered() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceTriggered() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED, routeInfo, undoCallback)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED, routeInfo, undoCallback)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToReceiverFailed() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
-
- /** Helper method providing default parameters to not clutter up the tests. */
- private fun transferToThisDeviceFailed() =
- ChipSenderInfo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED, routeInfo)
-
- private class TestMediaTttChipControllerSender(
- commandQueue: CommandQueue,
- context: Context,
- @MediaTttReceiverLogger logger: MediaTttLogger,
- windowManager: WindowManager,
- mainExecutor: DelayableExecutor,
- accessibilityManager: AccessibilityManager,
- configurationController: ConfigurationController,
- powerManager: PowerManager,
- uiEventLogger: MediaTttSenderUiEventLogger,
- falsingManager: FalsingManager,
- falsingCollector: FalsingCollector,
- viewUtil: ViewUtil,
- ) : MediaTttChipControllerSender(
- commandQueue,
- context,
- logger,
- windowManager,
- mainExecutor,
- accessibilityManager,
- configurationController,
- powerManager,
- uiEventLogger,
- falsingManager,
- falsingCollector,
- viewUtil,
- ) {
- override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
- // Just bypass the animation in tests
- onAnimationEnd.run()
- }
- }
-}
-
-private const val APP_NAME = "Fake app name"
-private const val OTHER_DEVICE_NAME = "My Tablet"
-private const val PACKAGE_NAME = "com.android.systemui"
-private const val TIMEOUT = 10000
-
-private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
- .addFeature("feature")
- .setClientPackageName(PACKAGE_NAME)
- .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
new file mode 100644
index 000000000000..fdeb3f5eb857
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -0,0 +1,691 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.taptotransfer.sender
+
+import android.app.StatusBarManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.media.MediaRoute2Info
+import android.os.PowerManager
+import android.os.VibrationEffect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.media.taptotransfer.MediaTttFlags
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.view.ViewUtil
+import com.google.common.truth.Truth.assertThat
+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.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class MediaTttSenderCoordinatorTest : SysuiTestCase() {
+
+ // Note: This tests are a bit like integration tests because they use a real instance of
+ // [ChipbarCoordinator] and verify that the coordinator displays the correct view, based on
+ // the inputs from [MediaTttSenderCoordinator].
+
+ private lateinit var underTest: MediaTttSenderCoordinator
+
+ @Mock private lateinit var accessibilityManager: AccessibilityManager
+ @Mock private lateinit var applicationInfo: ApplicationInfo
+ @Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var logger: MediaTttLogger
+ @Mock private lateinit var mediaTttFlags: MediaTttFlags
+ @Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var powerManager: PowerManager
+ @Mock private lateinit var viewUtil: ViewUtil
+ @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var vibratorHelper: VibratorHelper
+ private lateinit var chipbarCoordinator: ChipbarCoordinator
+ private lateinit var commandQueueCallback: CommandQueue.Callbacks
+ private lateinit var fakeAppIconDrawable: Drawable
+ private lateinit var fakeClock: FakeSystemClock
+ private lateinit var fakeExecutor: FakeExecutor
+ private lateinit var uiEventLoggerFake: UiEventLoggerFake
+ private lateinit var uiEventLogger: MediaTttSenderUiEventLogger
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
+ whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
+
+ fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
+ whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
+ whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
+ whenever(
+ packageManager.getApplicationInfo(
+ eq(PACKAGE_NAME),
+ any<PackageManager.ApplicationInfoFlags>()
+ )
+ )
+ .thenReturn(applicationInfo)
+ context.setMockPackageManager(packageManager)
+
+ fakeClock = FakeSystemClock()
+ fakeExecutor = FakeExecutor(fakeClock)
+
+ uiEventLoggerFake = UiEventLoggerFake()
+ uiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
+
+ chipbarCoordinator =
+ FakeChipbarCoordinator(
+ context,
+ logger,
+ windowManager,
+ fakeExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ falsingManager,
+ falsingCollector,
+ viewUtil,
+ vibratorHelper,
+ )
+ chipbarCoordinator.start()
+
+ underTest =
+ MediaTttSenderCoordinator(
+ chipbarCoordinator,
+ commandQueue,
+ context,
+ logger,
+ mediaTttFlags,
+ uiEventLogger,
+ )
+ underTest.start()
+
+ val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+ verify(commandQueue).addCallback(callbackCaptor.capture())
+ commandQueueCallback = callbackCaptor.value!!
+ }
+
+ @Test
+ fun commandQueueCallback_flagOff_noCallbackAdded() {
+ reset(commandQueue)
+ whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(false)
+ underTest =
+ MediaTttSenderCoordinator(
+ chipbarCoordinator,
+ commandQueue,
+ context,
+ logger,
+ mediaTttFlags,
+ uiEventLogger,
+ )
+ underTest.start()
+
+ verify(commandQueue, never()).addCallback(any())
+ }
+
+ @Test
+ fun commandQueueCallback_almostCloseToStartCast_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id)
+ verify(vibratorHelper).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id)
+ verify(vibratorHelper).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun commandQueueCallback_transferToReceiverTriggered_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id)
+ verify(vibratorHelper).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id)
+ verify(vibratorHelper).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
+ verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_nullUndoCallback_noUndo() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ null
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ },
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getUndoButton().hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
+ var undoCallbackCalled = false
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ },
+ )
+
+ getChipbarView().getUndoButton().performClick()
+
+ // Event index 1 since initially displaying the succeeded chip would also log an event
+ assertThat(uiEventLoggerFake.eventId(1))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id)
+ assertThat(undoCallbackCalled).isTrue()
+ assertThat(getChipbarView().getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
+ }
+
+ @Test
+ fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
+ verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ null
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ },
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getUndoButton().hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
+ var undoCallbackCalled = false
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {
+ undoCallbackCalled = true
+ }
+ },
+ )
+
+ getChipbarView().getUndoButton().performClick()
+
+ // Event index 1 since initially displaying the succeeded chip would also log an event
+ assertThat(uiEventLoggerFake.eventId(1))
+ .isEqualTo(
+ MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
+ )
+ assertThat(undoCallbackCalled).isTrue()
+ assertThat(getChipbarView().getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+ }
+
+ @Test
+ fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ routeInfo,
+ null
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
+ verify(vibratorHelper).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+ routeInfo,
+ null
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipbarView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_FAILED.getExpectedStateText())
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
+ verify(vibratorHelper).vibrate(any<VibrationEffect>())
+ }
+
+ @Test
+ fun commandQueueCallback_farFromReceiver_noChipShown() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER.id)
+ }
+
+ @Test
+ fun commandQueueCallback_almostCloseThenFarFromReceiver_chipShownThenHidden() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+
+ val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+ verify(windowManager).addView(viewCaptor.capture(), any())
+ verify(windowManager).removeView(viewCaptor.value)
+ }
+
+ @Test
+ fun commandQueueCallback_invalidStateParam_noChipShown() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(100, routeInfo, null)
+
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun receivesNewStateFromCommandQueue_isLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logStateChange(any(), any(), any())
+ }
+
+ @Test
+ fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayedButStillTimesOut() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ },
+ )
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
+
+ // Because [MediaTttSenderCoordinator] internally creates the undo callback, we should
+ // verify that the new state it triggers operates just like any other state.
+ getChipbarView().getUndoButton().performClick()
+ fakeExecutor.runAllReady()
+
+ // Verify that the click updated us to the triggered state
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ // Verify that we didn't remove the chipbar because it's in the triggered state
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ // Verify we eventually remove the chipbar
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ object : IUndoMediaTransferCallback.Stub() {
+ override fun onUndoTriggered() {}
+ },
+ )
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
+
+ // Because [MediaTttSenderCoordinator] internally creates the undo callback, we should
+ // verify that the new state it triggers operates just like any other state.
+ getChipbarView().getUndoButton().performClick()
+ fakeExecutor.runAllReady()
+
+ // Verify that the click updated us to the triggered state
+ assertThat(chipbarView.getChipText())
+ .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+ fakeExecutor.runAllReady()
+
+ // Verify that we didn't remove the chipbar because it's in the triggered state
+ verify(windowManager, never()).removeView(any())
+ verify(logger).logRemovalBypass(any(), any())
+
+ fakeClock.advanceTime(TIMEOUT + 1L)
+
+ // Verify we eventually remove the chipbar
+ verify(windowManager).removeView(any())
+ }
+
+ private fun getChipbarView(): ViewGroup {
+ val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+ verify(windowManager).addView(viewCaptor.capture(), any())
+ return viewCaptor.value as ViewGroup
+ }
+
+ private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.start_icon)
+
+ private fun ViewGroup.getChipText(): String =
+ (this.requireViewById<TextView>(R.id.text)).text as String
+
+ private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading)
+
+ private fun ViewGroup.getErrorIcon(): View = this.requireViewById(R.id.error)
+
+ private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.end_button)
+
+ private fun ChipStateSender.getExpectedStateText(): String? {
+ return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context)
+ }
+}
+
+private const val APP_NAME = "Fake app name"
+private const val OTHER_DEVICE_NAME = "My Tablet"
+private const val PACKAGE_NAME = "com.android.systemui"
+private const val TIMEOUT = 10000
+
+private val routeInfo =
+ MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
+ .addFeature("feature")
+ .setClientPackageName(PACKAGE_NAME)
+ .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
index 0badd861787d..1bc4719c70b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
@@ -147,6 +147,18 @@ public class ColorSchemeTest extends SysuiTestCase {
}
@Test
+ public void testMonochromatic() {
+ int colorInt = 0xffB3588A; // H350 C50 T50
+ ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
+ Style.MONOCHROMATIC /* style */);
+ int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+ Assert.assertTrue(
+ Color.red(neutralMid) == Color.green(neutralMid)
+ && Color.green(neutralMid) == Color.blue(neutralMid)
+ );
+ }
+
+ @Test
@SuppressWarnings("ResultOfMethodCallIgnored")
public void testToString() {
new ColorScheme(Color.TRANSPARENT, false /* darkTheme */).toString();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
index e2c6ff996199..d6db62aa2f72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
@@ -20,7 +20,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogcatEchoTracker
import com.android.systemui.statusbar.DisableFlagsLogger
import com.google.common.truth.Truth.assertThat
import org.junit.Test
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 d2c2d58820bc..cd7a949443c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -50,7 +50,7 @@ import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index b847ad07cd72..caf8321949ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -44,7 +44,7 @@ import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.customize.QSCustomizerController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index e539705d9ede..3c867ab32725 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -7,8 +7,8 @@ import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.media.MediaHost
-import com.android.systemui.media.MediaHostState
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.customize.QSCustomizerController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 1c686c66e31e..5e9c1aaad309 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -22,7 +22,6 @@ import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -52,6 +51,7 @@ import android.text.SpannableStringBuilder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.systemui.R;
@@ -97,6 +97,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
private static final int DEFAULT_ICON_ID = R.drawable.ic_info_outline;
private ViewGroup mRootView;
+ private ViewGroup mSecurityFooterView;
private TextView mFooterText;
private TestableImageView mPrimaryFooterIcon;
private QSSecurityFooter mFooter;
@@ -121,21 +122,26 @@ public class QSSecurityFooterTest extends SysuiTestCase {
Looper looper = mTestableLooper.getLooper();
Handler mainHandler = new Handler(looper);
when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
- mRootView = (ViewGroup) new LayoutInflaterBuilder(mContext)
+ mSecurityFooterView = (ViewGroup) new LayoutInflaterBuilder(mContext)
.replace("ImageView", TestableImageView.class)
.build().inflate(R.layout.quick_settings_security_footer, null, false);
mFooterUtils = new QSSecurityFooterUtils(getContext(),
getContext().getSystemService(DevicePolicyManager.class), mUserTracker,
mainHandler, mActivityStarter, mSecurityController, looper, mDialogLaunchAnimator);
- mFooter = new QSSecurityFooter(mRootView, mainHandler, mSecurityController, looper,
- mBroadcastDispatcher, mFooterUtils);
- mFooterText = mRootView.findViewById(R.id.footer_text);
- mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
+ mFooter = new QSSecurityFooter(mSecurityFooterView, mainHandler, mSecurityController,
+ looper, mBroadcastDispatcher, mFooterUtils);
+ mFooterText = mSecurityFooterView.findViewById(R.id.footer_text);
+ mPrimaryFooterIcon = mSecurityFooterView.findViewById(R.id.primary_footer_icon);
when(mSecurityController.getDeviceOwnerComponentOnAnyUser())
.thenReturn(DEVICE_OWNER_COMPONENT);
when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
+
+ // mSecurityFooterView must have a ViewGroup parent so that
+ // DialogLaunchAnimator.Controller.fromView() does not return null.
+ mRootView = new FrameLayout(mContext);
+ mRootView.addView(mSecurityFooterView);
ViewUtils.attachView(mRootView);
mFooter.init();
@@ -153,7 +159,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
- assertEquals(View.GONE, mRootView.getVisibility());
+ assertEquals(View.GONE, mSecurityFooterView.getVisibility());
}
@Test
@@ -165,7 +171,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
TestableLooper.get(this).processAllMessages();
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management),
mFooterText.getText());
- assertEquals(View.VISIBLE, mRootView.getVisibility());
+ assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
}
@@ -181,7 +187,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management,
MANAGING_ORGANIZATION),
mFooterText.getText());
- assertEquals(View.VISIBLE, mRootView.getVisibility());
+ assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
}
@@ -200,7 +206,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
assertEquals(mContext.getString(
R.string.quick_settings_financed_disclosure_named_management,
MANAGING_ORGANIZATION), mFooterText.getText());
- assertEquals(View.VISIBLE, mRootView.getVisibility());
+ assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
}
@@ -217,7 +223,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
- assertEquals(View.GONE, mRootView.getVisibility());
+ assertEquals(View.GONE, mSecurityFooterView.getVisibility());
}
@Test
@@ -227,8 +233,8 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
- assertFalse(mRootView.isClickable());
- assertEquals(View.GONE, mRootView.findViewById(R.id.footer_icon).getVisibility());
+ assertFalse(mSecurityFooterView.isClickable());
+ assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
}
@Test
@@ -241,8 +247,9 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
- assertTrue(mRootView.isClickable());
- assertEquals(View.VISIBLE, mRootView.findViewById(R.id.footer_icon).getVisibility());
+ assertTrue(mSecurityFooterView.isClickable());
+ assertEquals(View.VISIBLE,
+ mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
}
@Test
@@ -254,8 +261,8 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
- assertFalse(mRootView.isClickable());
- assertEquals(View.GONE, mRootView.findViewById(R.id.footer_icon).getVisibility());
+ assertFalse(mSecurityFooterView.isClickable());
+ assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
}
@Test
@@ -734,11 +741,11 @@ public class QSSecurityFooterTest extends SysuiTestCase {
@Test
public void testDialogUsesDialogLauncher() {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
- mFooter.onClick(mRootView);
+ mFooter.onClick(mSecurityFooterView);
mTestableLooper.processAllMessages();
- verify(mDialogLaunchAnimator).showFromView(any(), eq(mRootView), any());
+ verify(mDialogLaunchAnimator).show(any(), any());
}
@Test
@@ -775,7 +782,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
ArgumentCaptor<AlertDialog> dialogCaptor = ArgumentCaptor.forClass(AlertDialog.class);
mTestableLooper.processAllMessages();
- verify(mDialogLaunchAnimator).showFromView(dialogCaptor.capture(), any(), any());
+ verify(mDialogLaunchAnimator).show(dialogCaptor.capture(), any());
AlertDialog dialog = dialogCaptor.getValue();
dialog.create();
@@ -817,8 +824,8 @@ public class QSSecurityFooterTest extends SysuiTestCase {
verify(mBroadcastDispatcher).registerReceiverWithHandler(captor.capture(), any(), any(),
any());
- // Pretend view is not visible temporarily
- mRootView.onVisibilityAggregated(false);
+ // Pretend view is not attached anymore.
+ mRootView.removeView(mSecurityFooterView);
captor.getValue().onReceive(mContext,
new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG));
mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 3c58b6fc1354..c452872a527e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -52,6 +52,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.nano.SystemUIProtoDump;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
@@ -114,8 +115,6 @@ public class QSTileHostTest extends SysuiTestCase {
@Mock
private DumpManager mDumpManager;
@Mock
- private QSTile.State mMockState;
- @Mock
private CentralSurfaces mCentralSurfaces;
@Mock
private QSLogger mQSLogger;
@@ -195,7 +194,6 @@ public class QSTileHostTest extends SysuiTestCase {
}
private void setUpTileFactory() {
- when(mMockState.toString()).thenReturn(MOCK_STATE_STRING);
// Only create this kind of tiles
when(mDefaultFactory.createTile(anyString())).thenAnswer(
invocation -> {
@@ -209,7 +207,11 @@ public class QSTileHostTest extends SysuiTestCase {
} else if ("na".equals(spec)) {
return new NotAvailableTile(mQSTileHost);
} else if (CUSTOM_TILE_SPEC.equals(spec)) {
- return mCustomTile;
+ QSTile tile = mCustomTile;
+ QSTile.State s = mock(QSTile.State.class);
+ s.spec = spec;
+ when(mCustomTile.getState()).thenReturn(s);
+ return tile;
} else if ("internet".equals(spec)
|| "wifi".equals(spec)
|| "cell".equals(spec)) {
@@ -647,7 +649,7 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testSetTileRemoved_removedBySystem() {
int user = mUserTracker.getUserId();
- saveSetting("spec1" + CUSTOM_TILE_SPEC);
+ saveSetting("spec1," + CUSTOM_TILE_SPEC);
// This will be done by TileServiceManager
mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
@@ -658,6 +660,27 @@ public class QSTileHostTest extends SysuiTestCase {
.getBoolean(CUSTOM_TILE.flattenToString(), false));
}
+ @Test
+ public void testProtoDump_noTiles() {
+ SystemUIProtoDump proto = new SystemUIProtoDump();
+ mQSTileHost.dumpProto(proto, new String[0]);
+
+ assertEquals(0, proto.tiles.length);
+ }
+
+ @Test
+ public void testTilesInOrder() {
+ saveSetting("spec1," + CUSTOM_TILE_SPEC);
+
+ SystemUIProtoDump proto = new SystemUIProtoDump();
+ mQSTileHost.dumpProto(proto, new String[0]);
+
+ assertEquals(2, proto.tiles.length);
+ assertEquals("spec1", proto.tiles[0].getSpec());
+ assertEquals(CUSTOM_TILE.getPackageName(), proto.tiles[1].getComponentName().packageName);
+ assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className);
+ }
+
private SharedPreferences getSharedPreferenecesForUser(int user) {
return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
}
@@ -707,12 +730,9 @@ public class QSTileHostTest extends SysuiTestCase {
@Override
public State newTileState() {
- return mMockState;
- }
-
- @Override
- public State getState() {
- return mMockState;
+ State s = mock(QSTile.State.class);
+ when(s.toString()).thenReturn(MOCK_STATE_STRING);
+ return s;
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 6af8e4904a1e..f53e997a331c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -23,8 +23,8 @@ import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaHost
-import com.android.systemui.media.MediaHostState
+import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTileView
import com.android.systemui.qs.customize.QSCustomizerController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
new file mode 100644
index 000000000000..629c663943db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
@@ -0,0 +1,104 @@
+package com.android.systemui.qs
+
+import android.content.ComponentName
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.external.CustomTile
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TileStateToProtoTest : SysuiTestCase() {
+
+ companion object {
+ private const val TEST_LABEL = "label"
+ private const val TEST_SUBTITLE = "subtitle"
+ private const val TEST_SPEC = "spec"
+ private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls")
+ }
+
+ @Test
+ fun platformTile_INACTIVE() {
+ val state =
+ QSTile.State().apply {
+ spec = TEST_SPEC
+ label = TEST_LABEL
+ secondaryLabel = TEST_SUBTITLE
+ state = Tile.STATE_INACTIVE
+ }
+ val proto = state.toProto()
+
+ assertThat(proto).isNotNull()
+ assertThat(proto?.hasSpec()).isTrue()
+ assertThat(proto?.spec).isEqualTo(TEST_SPEC)
+ assertThat(proto?.hasComponentName()).isFalse()
+ assertThat(proto?.label).isEqualTo(TEST_LABEL)
+ assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+ assertThat(proto?.state).isEqualTo(Tile.STATE_INACTIVE)
+ assertThat(proto?.hasBooleanState()).isFalse()
+ }
+
+ @Test
+ fun componentTile_UNAVAILABLE() {
+ val state =
+ QSTile.State().apply {
+ spec = CustomTile.toSpec(TEST_COMPONENT)
+ label = TEST_LABEL
+ secondaryLabel = TEST_SUBTITLE
+ state = Tile.STATE_UNAVAILABLE
+ }
+ val proto = state.toProto()
+
+ assertThat(proto).isNotNull()
+ assertThat(proto?.hasSpec()).isFalse()
+ assertThat(proto?.hasComponentName()).isTrue()
+ val componentName = proto?.componentName
+ assertThat(componentName?.packageName).isEqualTo(TEST_COMPONENT.packageName)
+ assertThat(componentName?.className).isEqualTo(TEST_COMPONENT.className)
+ assertThat(proto?.label).isEqualTo(TEST_LABEL)
+ assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+ assertThat(proto?.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+ assertThat(proto?.hasBooleanState()).isFalse()
+ }
+
+ @Test
+ fun booleanState_ACTIVE() {
+ val state =
+ QSTile.BooleanState().apply {
+ spec = TEST_SPEC
+ label = TEST_LABEL
+ secondaryLabel = TEST_SUBTITLE
+ state = Tile.STATE_ACTIVE
+ value = true
+ }
+ val proto = state.toProto()
+
+ assertThat(proto).isNotNull()
+ assertThat(proto?.hasSpec()).isTrue()
+ assertThat(proto?.spec).isEqualTo(TEST_SPEC)
+ assertThat(proto?.hasComponentName()).isFalse()
+ assertThat(proto?.label).isEqualTo(TEST_LABEL)
+ assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+ assertThat(proto?.state).isEqualTo(Tile.STATE_ACTIVE)
+ assertThat(proto?.hasBooleanState()).isTrue()
+ assertThat(proto?.booleanState).isTrue()
+ }
+
+ @Test
+ fun noSpec_returnsNull() {
+ val state =
+ QSTile.State().apply {
+ label = TEST_LABEL
+ secondaryLabel = TEST_SUBTITLE
+ state = Tile.STATE_ACTIVE
+ }
+ val proto = state.toProto()
+
+ assertThat(proto).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
index 3c258077c29d..2c2ddbb9b8c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -23,13 +23,13 @@ import android.os.UserHandle
import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.nano.MetricsProto
import com.android.internal.logging.testing.FakeMetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
@@ -70,13 +70,13 @@ class FooterActionsInteractorTest : SysuiTestCase() {
val underTest = utils.footerActionsInteractor(qsSecurityFooterUtils = qsSecurityFooterUtils)
val quickSettingsContext = mock<Context>()
- underTest.showDeviceMonitoringDialog(quickSettingsContext)
- verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, null)
- val view = mock<View>()
- whenever(view.context).thenReturn(quickSettingsContext)
- underTest.showDeviceMonitoringDialog(view)
+ underTest.showDeviceMonitoringDialog(quickSettingsContext, null)
verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, null)
+
+ val expandable = mock<Expandable>()
+ underTest.showDeviceMonitoringDialog(quickSettingsContext, expandable)
+ verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, expandable)
}
@Test
@@ -85,8 +85,8 @@ class FooterActionsInteractorTest : SysuiTestCase() {
val underTest = utils.footerActionsInteractor(uiEventLogger = uiEventLogger)
val globalActionsDialogLite = mock<GlobalActionsDialogLite>()
- val view = mock<View>()
- underTest.showPowerMenuDialog(globalActionsDialogLite, view)
+ val expandable = mock<Expandable>()
+ underTest.showPowerMenuDialog(globalActionsDialogLite, expandable)
// Event is logged.
val logs = uiEventLogger.logs
@@ -99,7 +99,7 @@ class FooterActionsInteractorTest : SysuiTestCase() {
.showOrHideDialog(
/* keyguardShowing= */ false,
/* isDeviceProvisioned= */ true,
- view,
+ expandable,
)
}
@@ -167,11 +167,11 @@ class FooterActionsInteractorTest : SysuiTestCase() {
userSwitchDialogController = userSwitchDialogController,
)
- val view = mock<View>()
- underTest.showUserSwitcher(view)
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(context, expandable)
// Dialog is shown.
- verify(userSwitchDialogController).showDialog(view)
+ verify(userSwitchDialogController).showDialog(context, expandable)
}
@Test
@@ -184,12 +184,9 @@ class FooterActionsInteractorTest : SysuiTestCase() {
activityStarter = activityStarter,
)
- // The clicked view. The context is necessary because it's used to build the intent, that
- // we check below.
- val view = mock<View>()
- whenever(view.context).thenReturn(context)
-
- underTest.showUserSwitcher(view)
+ // The clicked expandable.
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(context, expandable)
// Dialog is shown.
val intentCaptor = argumentCaptor<Intent>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index da52a9b1a3c2..bc27bbc13f81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.qs.QSUserSwitcherEvent
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.source.UserRecord
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -139,6 +140,11 @@ class UserDetailViewAdapterTest : SysuiTestCase() {
clickableTest(false, false, mUserDetailItemView, true)
}
+ @Test
+ fun testManageUsersIsNotAvailable() {
+ assertNull(adapter.users.find { it.isManageUsers })
+ }
+
private fun createUserRecord(current: Boolean, guest: Boolean) =
UserRecord(
UserInfo(0 /* id */, "name", 0 /* flags */),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index 9d908fdfb976..0a34810f4d3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -20,12 +20,12 @@ import android.content.DialogInterface
import android.content.Intent
import android.provider.Settings
import android.testing.AndroidTestingRunner
-import android.view.View
import android.widget.Button
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PseudoGridView
@@ -35,6 +35,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,7 +64,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
@Mock
private lateinit var userDetailViewAdapter: UserDetailView.Adapter
@Mock
- private lateinit var launchView: View
+ private lateinit var launchExpandable: Expandable
@Mock
private lateinit var neutralButton: Button
@Mock
@@ -79,7 +80,6 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(launchView.context).thenReturn(mContext)
`when`(dialog.context).thenReturn(mContext)
controller = UserSwitchDialogController(
@@ -94,32 +94,34 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
@Test
fun showDialog_callsDialogShow() {
- controller.showDialog(launchView)
- verify(dialogLaunchAnimator).showFromView(eq(dialog), eq(launchView), any(), anyBoolean())
+ val launchController = mock<DialogLaunchAnimator.Controller>()
+ `when`(launchExpandable.dialogLaunchController(any())).thenReturn(launchController)
+ controller.showDialog(context, launchExpandable)
+ verify(dialogLaunchAnimator).show(eq(dialog), eq(launchController), anyBoolean())
verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
}
@Test
fun dialog_showForAllUsers() {
- controller.showDialog(launchView)
+ controller.showDialog(context, launchExpandable)
verify(dialog).setShowForAllUsers(true)
}
@Test
fun dialog_cancelOnTouchOutside() {
- controller.showDialog(launchView)
+ controller.showDialog(context, launchExpandable)
verify(dialog).setCanceledOnTouchOutside(true)
}
@Test
fun adapterAndGridLinked() {
- controller.showDialog(launchView)
+ controller.showDialog(context, launchExpandable)
verify(userDetailViewAdapter).linkToViewGroup(any<PseudoGridView>())
}
@Test
fun doneButtonLogsCorrectly() {
- controller.showDialog(launchView)
+ controller.showDialog(context, launchExpandable)
verify(dialog).setPositiveButton(anyInt(), capture(clickCaptor))
@@ -132,7 +134,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
fun clickSettingsButton_noFalsing_opensSettings() {
`when`(falsingManager.isFalseTap(anyInt())).thenReturn(false)
- controller.showDialog(launchView)
+ controller.showDialog(context, launchExpandable)
verify(dialog)
.setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
@@ -153,7 +155,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
fun clickSettingsButton_Falsing_notOpensSettings() {
`when`(falsingManager.isFalseTap(anyInt())).thenReturn(true)
- controller.showDialog(launchView)
+ controller.showDialog(context, launchExpandable)
verify(dialog)
.setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
new file mode 100644
index 000000000000..b6a595b0077a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+class ActionIntentCreatorTest : SysuiTestCase() {
+
+ @Test
+ fun testCreateShareIntent() {
+ val uri = Uri.parse("content://fake")
+ val subject = "Example subject"
+
+ val output = ActionIntentCreator.createShareIntent(uri, subject)
+
+ assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER)
+ assertFlagsSet(
+ Intent.FLAG_ACTIVITY_NEW_TASK or
+ Intent.FLAG_ACTIVITY_CLEAR_TASK or
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ output.flags
+ )
+
+ val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND)
+ assertThat(wrappedIntent?.data).isEqualTo(uri)
+ assertThat(wrappedIntent?.type).isEqualTo("image/png")
+ assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isEqualTo(subject)
+ assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
+ .isEqualTo(uri)
+ }
+
+ @Test
+ fun testCreateShareIntent_noSubject() {
+ val uri = Uri.parse("content://fake")
+ val output = ActionIntentCreator.createShareIntent(uri, null)
+ val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull()
+ }
+
+ @Test
+ fun testCreateEditIntent() {
+ val uri = Uri.parse("content://fake")
+ val context = mock<Context>()
+
+ val output = ActionIntentCreator.createEditIntent(uri, context)
+
+ assertThat(output.action).isEqualTo(Intent.ACTION_EDIT)
+ assertThat(output.data).isEqualTo(uri)
+ assertThat(output.type).isEqualTo("image/png")
+ assertThat(output.component).isNull()
+ val expectedFlags =
+ Intent.FLAG_GRANT_READ_URI_PERMISSION or
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
+ Intent.FLAG_ACTIVITY_NEW_TASK or
+ Intent.FLAG_ACTIVITY_CLEAR_TASK
+ assertFlagsSet(expectedFlags, output.flags)
+ }
+
+ @Test
+ fun testCreateEditIntent_withEditor() {
+ val uri = Uri.parse("content://fake")
+ val context = mock<Context>()
+ var component = ComponentName("com.android.foo", "com.android.foo.Something")
+
+ whenever(context.getString(eq(R.string.config_screenshotEditor)))
+ .thenReturn(component.flattenToString())
+
+ val output = ActionIntentCreator.createEditIntent(uri, context)
+
+ assertThat(output.component).isEqualTo(component)
+ }
+
+ private fun assertFlagsSet(expected: Int, observed: Int) {
+ assertThat(observed and expected).isEqualTo(expected)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
new file mode 100644
index 000000000000..1130bda9b881
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness
+
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+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.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class BrightnessDialogTest : SysuiTestCase() {
+
+ @Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
+ @Mock private lateinit var backgroundHandler: Handler
+ @Mock private lateinit var brightnessSliderController: BrightnessSliderController
+
+ @Rule
+ @JvmField
+ var activityRule =
+ ActivityTestRule(
+ object : SingleActivityFactory<TestDialog>(TestDialog::class.java) {
+ override fun create(intent: Intent?): TestDialog {
+ return TestDialog(
+ fakeBroadcastDispatcher,
+ brightnessSliderControllerFactory,
+ backgroundHandler
+ )
+ }
+ },
+ false,
+ false
+ )
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ `when`(brightnessSliderControllerFactory.create(any(), any()))
+ .thenReturn(brightnessSliderController)
+ `when`(brightnessSliderController.rootView).thenReturn(View(context))
+
+ activityRule.launchActivity(null)
+ }
+
+ @After
+ fun tearDown() {
+ activityRule.finishActivity()
+ }
+
+ @Test
+ fun testGestureExclusion() {
+ val frame = activityRule.activity.requireViewById<View>(R.id.brightness_mirror_container)
+
+ val lp = frame.layoutParams as ViewGroup.MarginLayoutParams
+ val horizontalMargin =
+ activityRule.activity.resources.getDimensionPixelSize(
+ R.dimen.notification_side_paddings
+ )
+ assertThat(lp.leftMargin).isEqualTo(horizontalMargin)
+ assertThat(lp.rightMargin).isEqualTo(horizontalMargin)
+
+ assertThat(frame.systemGestureExclusionRects.size).isEqualTo(1)
+ val exclusion = frame.systemGestureExclusionRects[0]
+ assertThat(exclusion)
+ .isEqualTo(Rect(-horizontalMargin, 0, frame.width + horizontalMargin, frame.height))
+ }
+
+ class TestDialog(
+ broadcastDispatcher: BroadcastDispatcher,
+ brightnessSliderControllerFactory: BrightnessSliderController.Factory,
+ backgroundHandler: Handler
+ ) : BrightnessDialog(broadcastDispatcher, brightnessSliderControllerFactory, backgroundHandler)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index c76d9e7a2b20..14a3bc147808 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -645,6 +645,65 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
verify(animator).start()
}
+ @Test
+ fun privacyChipParentVisibleFromStart() {
+ verify(privacyIconsController).onParentVisible()
+ }
+
+ @Test
+ fun privacyChipParentVisibleAlways() {
+ controller.largeScreenActive = true
+ controller.largeScreenActive = false
+ controller.largeScreenActive = true
+
+ verify(privacyIconsController, never()).onParentInvisible()
+ }
+
+ @Test
+ fun clockPivotYInCenter() {
+ val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+ verify(clock).addOnLayoutChangeListener(capture(captor))
+ var height = 100
+ val width = 50
+
+ clock.executeLayoutChange(0, 0, width, height, captor.value)
+ verify(clock).pivotY = height.toFloat() / 2
+
+ height = 150
+ clock.executeLayoutChange(0, 0, width, height, captor.value)
+ verify(clock).pivotY = height.toFloat() / 2
+ }
+
+ private fun View.executeLayoutChange(
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ listener: View.OnLayoutChangeListener
+ ) {
+ val oldLeft = this.left
+ val oldTop = this.top
+ val oldRight = this.right
+ val oldBottom = this.bottom
+ whenever(this.left).thenReturn(left)
+ whenever(this.top).thenReturn(top)
+ whenever(this.right).thenReturn(right)
+ whenever(this.bottom).thenReturn(bottom)
+ whenever(this.height).thenReturn(bottom - top)
+ whenever(this.width).thenReturn(right - left)
+ listener.onLayoutChange(
+ this,
+ oldLeft,
+ oldTop,
+ oldRight,
+ oldBottom,
+ left,
+ top,
+ right,
+ bottom
+ )
+ }
+
private fun createWindowInsets(
topCutout: Rect? = Rect()
): WindowInsets {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 37be3439fdc8..02f28a235b95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.shade;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
@@ -33,11 +34,13 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -76,6 +79,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.keyguard.KeyguardClockSwitch;
import com.android.keyguard.KeyguardClockSwitchController;
import com.android.keyguard.KeyguardStatusView;
@@ -93,7 +97,6 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
@@ -102,14 +105,15 @@ import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
-import com.android.systemui.media.KeyguardMediaController;
-import com.android.systemui.media.MediaDataManager;
-import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.transition.ShadeTransitionController;
@@ -165,7 +169,6 @@ import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
-import com.android.systemui.wallet.controller.QuickAccessWalletController;
import com.android.wm.shell.animation.FlingAnimationUtils;
import org.junit.After;
@@ -173,6 +176,8 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
@@ -251,17 +256,15 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock private KeyguardMediaController mKeyguardMediaController;
@Mock private PrivacyDotViewController mPrivacyDotViewController;
@Mock private NavigationModeController mNavigationModeController;
+ @Mock private NavigationBarController mNavigationBarController;
@Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
@Mock private ContentResolver mContentResolver;
@Mock private TapAgainViewController mTapAgainViewController;
@Mock private KeyguardIndicationController mKeyguardIndicationController;
@Mock private FragmentService mFragmentService;
@Mock private FragmentHostManager mFragmentHostManager;
- @Mock private QuickAccessWalletController mQuickAccessWalletController;
- @Mock private QRCodeScannerController mQrCodeScannerController;
@Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
@Mock private RecordingController mRecordingController;
- @Mock private ControlsComponent mControlsComponent;
@Mock private LockscreenGestureLogger mLockscreenGestureLogger;
@Mock private DumpManager mDumpManager;
@Mock private InteractionJankMonitor mInteractionJankMonitor;
@@ -282,6 +285,10 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock private ViewTreeObserver mViewTreeObserver;
@Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
@Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
+ @Mock private MotionEvent mDownMotionEvent;
+ @Captor
+ private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
+ mEmptySpaceClickListenerCaptor;
private NotificationPanelViewController.TouchHandler mTouchHandler;
private ConfigurationController mConfigurationController;
@@ -373,6 +380,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
+ mDumpManager,
mock(HeadsUpManagerPhone.class),
new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
mInteractionJankMonitor),
@@ -388,6 +396,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mConfigurationController,
mStatusBarStateController,
mFalsingManager,
+ mShadeExpansionStateManager,
mLockscreenShadeTransitionController,
new FalsingCollectorFake(),
mDumpManager);
@@ -425,6 +434,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
when(mView.getParent()).thenReturn(mViewParent);
when(mQs.getHeader()).thenReturn(mQsHeader);
+ when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);
+ when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
mMainHandler = new Handler(Looper.getMainLooper());
NotificationPanelViewController.PanelEventsEmitter panelEventsEmitter =
@@ -468,6 +479,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mPrivacyDotViewController,
mTapAgainViewController,
mNavigationModeController,
+ mNavigationBarController,
mFragmentService,
mContentResolver,
mRecordingController,
@@ -512,6 +524,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
.addCallback(mNotificationPanelViewController.mStatusBarStateListener);
mNotificationPanelViewController
.setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
+ verify(mNotificationStackScrollLayoutController)
+ .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());
}
@After
@@ -716,6 +730,72 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
+ public void test_pulsing_onTouchEvent_noTracking() {
+ // GIVEN device is pulsing
+ mNotificationPanelViewController.setPulsing(true);
+
+ // WHEN touch DOWN & MOVE events received
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
+ 0 /* metaState */));
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
+ 0 /* metaState */));
+
+ // THEN touch is NOT tracked (since the device is pulsing)
+ assertThat(mNotificationPanelViewController.isTracking()).isFalse();
+ }
+
+ @Test
+ public void test_onTouchEvent_startTracking() {
+ // GIVEN device is NOT pulsing
+ mNotificationPanelViewController.setPulsing(false);
+
+ // WHEN touch DOWN & MOVE events received
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
+ 0 /* metaState */));
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
+ 0 /* metaState */));
+
+ // THEN touch is tracked
+ assertThat(mNotificationPanelViewController.isTracking()).isTrue();
+ }
+
+ @Test
+ public void testOnTouchEvent_expansionResumesAfterBriefTouch() {
+ // Start shade collapse with swipe up
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
+ 0 /* metaState */));
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */,
+ 0 /* metaState */));
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
+ 0 /* metaState */));
+
+ assertThat(mNotificationPanelViewController.getClosing()).isTrue();
+ assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
+
+ // simulate touch that does not exceed touch slop
+ onTouchEvent(MotionEvent.obtain(2L /* downTime */,
+ 2L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 300f /* y */,
+ 0 /* metaState */));
+
+ mNotificationPanelViewController.setTouchSlopExceeded(false);
+
+ onTouchEvent(MotionEvent.obtain(2L /* downTime */,
+ 2L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
+ 0 /* metaState */));
+
+ // fling should still be called after a touch that does not exceed touch slop
+ assertThat(mNotificationPanelViewController.getClosing()).isTrue();
+ assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
+ }
+
+ @Test
public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
when(mCommandQueue.panelsEnabled()).thenReturn(false);
@@ -1506,6 +1586,103 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
);
}
+ @Test
+ public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() {
+ StatusBarStateController.StateListener statusBarStateListener =
+ mNotificationPanelViewController.mStatusBarStateListener;
+ statusBarStateListener.onStateChanged(KEYGUARD);
+ mNotificationPanelViewController.setDozing(false, false);
+
+ // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+ mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+ mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+ verify(mUpdateMonitor).requestFaceAuth(true,
+ FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
+ }
+
+ @Test
+ public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() {
+ StatusBarStateController.StateListener statusBarStateListener =
+ mNotificationPanelViewController.mStatusBarStateListener;
+ statusBarStateListener.onStateChanged(KEYGUARD);
+ mNotificationPanelViewController.setDozing(false, false);
+ when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
+
+ // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+ mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+ mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+ verify(mNotificationStackScrollLayoutController).setUnlockHintRunning(true);
+ }
+
+ @Test
+ public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() {
+ StatusBarStateController.StateListener statusBarStateListener =
+ mNotificationPanelViewController.mStatusBarStateListener;
+ statusBarStateListener.onStateChanged(KEYGUARD);
+ mNotificationPanelViewController.setDozing(false, false);
+ when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
+
+ // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+ mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+ mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+ verify(mNotificationStackScrollLayoutController, never()).setUnlockHintRunning(true);
+ }
+
+ @Test
+ public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() {
+ StatusBarStateController.StateListener statusBarStateListener =
+ mNotificationPanelViewController.mStatusBarStateListener;
+ statusBarStateListener.onStateChanged(KEYGUARD);
+ mNotificationPanelViewController.setDozing(true, false);
+
+ // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+ mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+ mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+ verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+ }
+
+ @Test
+ public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() {
+ StatusBarStateController.StateListener statusBarStateListener =
+ mNotificationPanelViewController.mStatusBarStateListener;
+ statusBarStateListener.onStateChanged(SHADE_LOCKED);
+
+ mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+
+ verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+
+ }
+
+ /**
+ * When shade is flinging to close and this fling is not intercepted,
+ * {@link AmbientState#setIsClosing(boolean)} should be called before
+ * {@link NotificationStackScrollLayoutController#onExpansionStopped()}
+ * to ensure scrollY can be correctly set to be 0
+ */
+ @Test
+ public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() {
+ // Given: Shade is expanded
+ mNotificationPanelViewController.notifyExpandingFinished();
+ mNotificationPanelViewController.setIsClosing(false);
+
+ // When: Shade flings to close not canceled
+ mNotificationPanelViewController.notifyExpandingStarted();
+ mNotificationPanelViewController.setIsClosing(true);
+ mNotificationPanelViewController.onFlingEnd(false);
+
+ // Then: AmbientState's mIsClosing should be set to false
+ // before mNotificationStackScrollLayoutController.onExpansionStopped() is called
+ // to ensure NotificationStackScrollLayout.resetScrollPosition() -> resetScrollPosition
+ // -> setOwnScrollY(0) can set scrollY to 0 when shade is closed
+ InOrder inOrder = inOrder(mAmbientState, mNotificationStackScrollLayoutController);
+ inOrder.verify(mAmbientState).setIsClosing(false);
+ inOrder.verify(mNotificationStackScrollLayoutController).onExpansionStopped();
+ }
+
private static MotionEvent createMotionEvent(int x, int y, int action) {
return MotionEvent.obtain(
/* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
index 12ef036d89d0..bdafc7df33bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
@@ -66,6 +66,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
@Mock
private lateinit var largeScreenShadeHeaderController: LargeScreenShadeHeaderController
@Mock
+ private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock
private lateinit var featureFlags: FeatureFlags
@Captor
lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
@@ -96,6 +98,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
navigationModeController,
overviewProxyService,
largeScreenShadeHeaderController,
+ shadeExpansionStateManager,
featureFlags,
delayableExecutor
)
@@ -380,6 +383,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
navigationModeController,
overviewProxyService,
largeScreenShadeHeaderController,
+ shadeExpansionStateManager,
featureFlags,
delayableExecutor
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index ad3d3d2958cb..95cf9d60b511 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -88,6 +88,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private ScreenOffAnimationController mScreenOffAnimationController;
@Mock private AuthController mAuthController;
+ @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
@Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
@@ -103,7 +104,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
mColorExtractor, mDumpManager, mKeyguardStateController,
- mScreenOffAnimationController, mAuthController) {
+ mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager) {
@Override
protected boolean isDebuggable() {
return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
index eb34561d15a0..cc45cf88fa18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.TextAnimator
+import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -55,7 +56,7 @@ class AnimatableClockViewTest : SysuiTestCase() {
clockView.animateAppearOnLockscreen()
clockView.measure(50, 50)
- verify(mockTextAnimator).glyphFilter = null
+ verify(mockTextAnimator).glyphFilter = any()
verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null)
verifyNoMoreInteractions(mockTextAnimator)
}
@@ -66,7 +67,7 @@ class AnimatableClockViewTest : SysuiTestCase() {
clockView.measure(50, 50)
clockView.animateAppearOnLockscreen()
- verify(mockTextAnimator, times(2)).glyphFilter = null
+ verify(mockTextAnimator, times(2)).glyphFilter = any()
verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null)
verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null)
verifyNoMoreInteractions(mockTextAnimator)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index cf5fa87272c7..64dc9568030b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -16,6 +16,11 @@
package com.android.systemui.shared.system;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.view.RemoteAnimationTarget.MODE_CHANGING;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -25,11 +30,6 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_STANDARD;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CHANGING;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -40,6 +40,7 @@ import android.app.ActivityManager;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -73,12 +74,12 @@ public class RemoteTransitionTest extends SysuiTestCase {
.addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER, null /* taskInfo */)
.addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */).build();
// Check apps extraction
- RemoteAnimationTargetCompat[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined,
+ RemoteAnimationTarget[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined,
mock(SurfaceControl.Transaction.class), null /* leashes */);
assertEquals(2, wrapped.length);
int changeLayer = -1;
int closeLayer = -1;
- for (RemoteAnimationTargetCompat t : wrapped) {
+ for (RemoteAnimationTarget t : wrapped) {
if (t.mode == MODE_CHANGING) {
changeLayer = t.prefixOrderIndex;
} else if (t.mode == MODE_CLOSING) {
@@ -91,14 +92,14 @@ public class RemoteTransitionTest extends SysuiTestCase {
assertTrue(closeLayer < changeLayer);
// Check wallpaper extraction
- RemoteAnimationTargetCompat[] wallps = RemoteAnimationTargetCompat.wrapNonApps(combined,
+ RemoteAnimationTarget[] wallps = RemoteAnimationTargetCompat.wrapNonApps(combined,
true /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);
assertEquals(1, wallps.length);
assertTrue(wallps[0].prefixOrderIndex < closeLayer);
assertEquals(MODE_OPENING, wallps[0].mode);
// Check non-apps extraction
- RemoteAnimationTargetCompat[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(combined,
+ RemoteAnimationTarget[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(combined,
false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);
assertEquals(1, nonApps.length);
assertTrue(nonApps[0].prefixOrderIndex < closeLayer);
@@ -115,9 +116,9 @@ public class RemoteTransitionTest extends SysuiTestCase {
change.setTaskInfo(createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_HOME));
change.setEndAbsBounds(endBounds);
change.setEndRelOffset(0, 0);
- final RemoteAnimationTargetCompat wrapped = new RemoteAnimationTargetCompat(change,
- 0 /* order */, tinfo, mock(SurfaceControl.Transaction.class));
- assertEquals(ACTIVITY_TYPE_HOME, wrapped.activityType);
+ RemoteAnimationTarget wrapped = RemoteAnimationTargetCompat.newTarget(
+ change, 0 /* order */, tinfo, mock(SurfaceControl.Transaction.class), null);
+ assertEquals(ACTIVITY_TYPE_HOME, wrapped.windowConfiguration.getActivityType());
assertEquals(new Rect(0, 0, 100, 140), wrapped.localBounds);
assertEquals(endBounds, wrapped.screenSpaceBounds);
assertTrue(wrapped.isTranslucent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
index 5b34a95d4fb0..b761647e24e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
@@ -17,58 +17,58 @@ import org.mockito.MockitoAnnotations
@SmallTest
class UncaughtExceptionPreHandlerTest : SysuiTestCase() {
- private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager
+ private lateinit var preHandlerManager: UncaughtExceptionPreHandlerManager
- @Mock private lateinit var mockHandler: UncaughtExceptionHandler
+ @Mock private lateinit var mockHandler: UncaughtExceptionHandler
- @Mock private lateinit var mockHandler2: UncaughtExceptionHandler
+ @Mock private lateinit var mockHandler2: UncaughtExceptionHandler
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- Thread.setUncaughtExceptionPreHandler(null)
- preHandlerManager = UncaughtExceptionPreHandlerManager()
- }
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ Thread.setUncaughtExceptionPreHandler(null)
+ preHandlerManager = UncaughtExceptionPreHandlerManager()
+ }
- @Test
- fun registerHandler_registersOnceOnly() {
- preHandlerManager.registerHandler(mockHandler)
- preHandlerManager.registerHandler(mockHandler)
- preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
- verify(mockHandler, only()).uncaughtException(any(), any())
- }
+ @Test
+ fun registerHandler_registersOnceOnly() {
+ preHandlerManager.registerHandler(mockHandler)
+ preHandlerManager.registerHandler(mockHandler)
+ preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
+ verify(mockHandler, only()).uncaughtException(any(), any())
+ }
- @Test
- fun registerHandler_setsUncaughtExceptionPreHandler() {
- Thread.setUncaughtExceptionPreHandler(null)
- preHandlerManager.registerHandler(mockHandler)
- assertThat(Thread.getUncaughtExceptionPreHandler()).isNotNull()
- }
+ @Test
+ fun registerHandler_setsUncaughtExceptionPreHandler() {
+ Thread.setUncaughtExceptionPreHandler(null)
+ preHandlerManager.registerHandler(mockHandler)
+ assertThat(Thread.getUncaughtExceptionPreHandler()).isNotNull()
+ }
- @Test
- fun registerHandler_preservesOriginalHandler() {
- Thread.setUncaughtExceptionPreHandler(mockHandler)
- preHandlerManager.registerHandler(mockHandler2)
- preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
- verify(mockHandler, only()).uncaughtException(any(), any())
- }
+ @Test
+ fun registerHandler_preservesOriginalHandler() {
+ Thread.setUncaughtExceptionPreHandler(mockHandler)
+ preHandlerManager.registerHandler(mockHandler2)
+ preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
+ verify(mockHandler, only()).uncaughtException(any(), any())
+ }
- @Test
- @Ignore
- fun registerHandler_toleratesHandlersThatThrow() {
- `when`(mockHandler2.uncaughtException(any(), any())).thenThrow(RuntimeException())
- preHandlerManager.registerHandler(mockHandler2)
- preHandlerManager.registerHandler(mockHandler)
- preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
- verify(mockHandler2, only()).uncaughtException(any(), any())
- verify(mockHandler, only()).uncaughtException(any(), any())
- }
+ @Test
+ @Ignore
+ fun registerHandler_toleratesHandlersThatThrow() {
+ `when`(mockHandler2.uncaughtException(any(), any())).thenThrow(RuntimeException())
+ preHandlerManager.registerHandler(mockHandler2)
+ preHandlerManager.registerHandler(mockHandler)
+ preHandlerManager.handleUncaughtException(Thread.currentThread(), Exception())
+ verify(mockHandler2, only()).uncaughtException(any(), any())
+ verify(mockHandler, only()).uncaughtException(any(), any())
+ }
- @Test
- fun registerHandler_doesNotSetUpTwice() {
- UncaughtExceptionPreHandlerManager().registerHandler(mockHandler2)
- assertThrows(IllegalStateException::class.java) {
- preHandlerManager.registerHandler(mockHandler)
+ @Test
+ fun registerHandler_doesNotSetUpTwice() {
+ UncaughtExceptionPreHandlerManager().registerHandler(mockHandler2)
+ assertThrows(IllegalStateException::class.java) {
+ preHandlerManager.registerHandler(mockHandler)
+ }
}
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
index 8cb530c355bd..5fc0ffe42f55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
@@ -4,7 +4,7 @@ import android.testing.AndroidTestingRunner
import android.util.DisplayMetrics
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
import com.android.systemui.statusbar.phone.LockscreenGestureLogger
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 8643e86acef2..3d11ced6207d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -10,7 +10,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.media.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.shade.NotificationPanelViewController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
index 44cbe51a30ac..fbb8ebfb3e3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
@@ -56,6 +57,7 @@ class PulseExpansionHandlerTest : SysuiTestCase() {
private val configurationController: ConfigurationController = mock()
private val statusBarStateController: StatusBarStateController = mock()
private val falsingManager: FalsingManager = mock()
+ private val shadeExpansionStateManager: ShadeExpansionStateManager = mock()
private val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock()
private val falsingCollector: FalsingCollector = mock()
private val dumpManager: DumpManager = mock()
@@ -65,7 +67,8 @@ class PulseExpansionHandlerTest : SysuiTestCase() {
fun setUp() {
whenever(expandableView.collapsedHeight).thenReturn(collapsedHeight)
- pulseExpansionHandler = PulseExpansionHandler(
+ pulseExpansionHandler =
+ PulseExpansionHandler(
mContext,
wakeUpCoordinator,
bypassController,
@@ -74,10 +77,11 @@ class PulseExpansionHandlerTest : SysuiTestCase() {
configurationController,
statusBarStateController,
falsingManager,
+ shadeExpansionStateManager,
lockscreenShadeTransitionController,
falsingCollector,
dumpManager
- )
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index f8a0d2fc415c..9c65fac1af45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -70,7 +70,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.telephony.TelephonyListenerManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index ed8a3e16cdd1..4bed4a19b3d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -38,7 +38,7 @@ import android.testing.TestableLooper.RunWithLooper;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index a76676e01c15..d5f5105036d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -43,7 +43,7 @@ import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index 4b458f5a9123..dda7fadde2d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -31,8 +31,8 @@ public class GroupEntryBuilder {
private long mCreationTime = 0;
@Nullable private GroupEntry mParent = GroupEntry.ROOT_ENTRY;
private NotifSection mNotifSection;
- private NotificationEntry mSummary = null;
- private List<NotificationEntry> mChildren = new ArrayList<>();
+ @Nullable private NotificationEntry mSummary = null;
+ private final List<NotificationEntry> mChildren = new ArrayList<>();
/** Builds a new instance of GroupEntry */
public GroupEntry build() {
@@ -41,7 +41,9 @@ public class GroupEntryBuilder {
ge.getAttachState().setSection(mNotifSection);
ge.setSummary(mSummary);
- mSummary.setParent(ge);
+ if (mSummary != null) {
+ mSummary.setParent(ge);
+ }
for (NotificationEntry child : mChildren) {
ge.addChild(child);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 851517e1e35b..3b05321e1a6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -1498,45 +1498,8 @@ public class NotifCollectionTest extends SysuiTestCase {
}
@Test
- public void testMissingRankingWhenRemovalFeatureIsDisabled() {
+ public void testMissingRanking() {
// GIVEN a pipeline with one two notifications
- when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(false);
- String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key;
- String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key;
- NotificationEntry entry1 = mCollectionListener.getEntry(key1);
- NotificationEntry entry2 = mCollectionListener.getEntry(key2);
- clearInvocations(mCollectionListener);
-
- // GIVEN the message for removing key1 gets does not reach NotifCollection
- Ranking ranking1 = mNoMan.removeRankingWithoutEvent(key1);
- // WHEN the message for removing key2 arrives
- mNoMan.retractNotif(entry2.getSbn(), REASON_APP_CANCEL);
-
- // THEN only entry2 gets removed
- verify(mCollectionListener).onEntryRemoved(eq(entry2), eq(REASON_APP_CANCEL));
- verify(mCollectionListener).onEntryCleanUp(eq(entry2));
- verify(mCollectionListener).onRankingApplied();
- verifyNoMoreInteractions(mCollectionListener);
- verify(mLogger).logMissingRankings(eq(List.of(entry1)), eq(1), any());
- verify(mLogger, never()).logRecoveredRankings(any(), anyInt());
- clearInvocations(mCollectionListener, mLogger);
-
- // WHEN a ranking update includes key1 again
- mNoMan.setRanking(key1, ranking1);
- mNoMan.issueRankingUpdate();
-
- // VERIFY that we do nothing but log the 'recovery'
- verify(mCollectionListener).onRankingUpdate(any());
- verify(mCollectionListener).onRankingApplied();
- verifyNoMoreInteractions(mCollectionListener);
- verify(mLogger, never()).logMissingRankings(any(), anyInt(), any());
- verify(mLogger).logRecoveredRankings(eq(List.of(key1)), eq(0));
- }
-
- @Test
- public void testMissingRankingWhenRemovalFeatureIsEnabled() {
- // GIVEN a pipeline with one two notifications
- when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(true);
String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key;
String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key;
NotificationEntry entry1 = mCollectionListener.getEntry(key1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 82e32b2fdc64..09f8a10f88c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -34,10 +34,12 @@ import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
@@ -135,6 +137,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
allowTestableLooperAsMainThread();
+ when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(true);
mListBuilder = new ShadeListBuilder(
mDumpManager,
@@ -1995,22 +1998,89 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
@Test
+ public void testActiveOrdering_withLegacyStability() {
+ when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(false);
+ assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); // no change
+ assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG", true); // X
+ assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG", true); // no change
+ assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG", true); // Z and X
+ assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG", true); // Z and X + gap
+ }
+
+ @Test
+ public void testStableOrdering_withLegacyStability() {
+ when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(false);
+ mStabilityManager.setAllowEntryReordering(false);
+ assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); // no change
+ assertOrder("ABCDEFG", "ACDEFXBG", "XABCDEFG", false); // X
+ assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG", false); // no change
+ assertOrder("ABCDEFG", "ACDEFBXZG", "XZABCDEFG", false); // Z and X
+ assertOrder("ABCDEFG", "AXCDEZFBG", "XZABCDEFG", false); // Z and X + gap
+ }
+
+ @Test
public void testStableOrdering() {
+ when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);
mStabilityManager.setAllowEntryReordering(false);
- assertOrder("ABCDEFG", "ACDEFXBG", "XABCDEFG"); // X
- assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG"); // no change
- assertOrder("ABCDEFG", "ACDEFBXZG", "XZABCDEFG"); // Z and X
- assertOrder("ABCDEFG", "AXCDEZFBG", "XZABCDEFG"); // Z and X + gap
- verify(mStabilityManager, times(4)).onEntryReorderSuppressed();
+ // No input or output
+ assertOrder("", "", "", true);
+ // Remove everything
+ assertOrder("ABCDEFG", "", "", true);
+ // Literally no changes
+ assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true);
+
+ // No stable order
+ assertOrder("", "ABCDEFG", "ABCDEFG", true);
+
+ // F moved after A, and...
+ assertOrder("ABCDEFG", "AFBCDEG", "ABCDEFG", false); // No other changes
+ assertOrder("ABCDEFG", "AXFBCDEG", "AXBCDEFG", false); // Insert X before F
+ assertOrder("ABCDEFG", "AFXBCDEG", "AXBCDEFG", false); // Insert X after F
+ assertOrder("ABCDEFG", "AFBCDEXG", "ABCDEFXG", false); // Insert X where F was
+
+ // B moved after F, and...
+ assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG", false); // No other changes
+ assertOrder("ABCDEFG", "ACDEFXBG", "ABCDEFXG", false); // Insert X before B
+ assertOrder("ABCDEFG", "ACDEFBXG", "ABCDEFXG", false); // Insert X after B
+ assertOrder("ABCDEFG", "AXCDEFBG", "AXBCDEFG", false); // Insert X where B was
+
+ // Swap F and B, and...
+ assertOrder("ABCDEFG", "AFCDEBG", "ABCDEFG", false); // No other changes
+ assertOrder("ABCDEFG", "AXFCDEBG", "AXBCDEFG", false); // Insert X before F
+ assertOrder("ABCDEFG", "AFXCDEBG", "AXBCDEFG", false); // Insert X after F
+ assertOrder("ABCDEFG", "AFCXDEBG", "AXBCDEFG", false); // Insert X between CD (or: ABCXDEFG)
+ assertOrder("ABCDEFG", "AFCDXEBG", "ABCDXEFG", false); // Insert X between DE (or: ABCDEFXG)
+ assertOrder("ABCDEFG", "AFCDEXBG", "ABCDEFXG", false); // Insert X before B
+ assertOrder("ABCDEFG", "AFCDEBXG", "ABCDEFXG", false); // Insert X after B
+
+ // Remove a bunch of entries at once
+ assertOrder("ABCDEFGHIJKL", "ACEGHI", "ACEGHI", true);
+
+ // Remove a bunch of entries and scramble
+ assertOrder("ABCDEFGHIJKL", "GCEHAI", "ACEGHI", false);
+
+ // Add a bunch of entries at once
+ assertOrder("ABCDEFG", "AVBWCXDYZEFG", "AVBWCXDYZEFG", true);
+
+ // Add a bunch of entries and reverse originals
+ // NOTE: Some of these don't have obviously correct answers
+ assertOrder("ABCDEFG", "GFEBCDAVWXYZ", "ABCDEFGVWXYZ", false); // appended
+ assertOrder("ABCDEFG", "VWXYZGFEBCDA", "VWXYZABCDEFG", false); // prepended
+ assertOrder("ABCDEFG", "GFEBVWXYZCDA", "ABCDEFGVWXYZ", false); // closer to back: append
+ assertOrder("ABCDEFG", "GFEVWXYZBCDA", "VWXYZABCDEFG", false); // closer to front: prepend
+ assertOrder("ABCDEFG", "GFEVWBXYZCDA", "VWABCDEFGXYZ", false); // split new entries
+
+ // Swap 2 pairs ("*BC*NO*"->"*NO*CB*"), remove EG, add UVWXYZ throughout
+ assertOrder("ABCDEFGHIJKLMNOP", "AUNOVDFHWXIJKLMYCBZP", "AUVBCDFHWXIJKLMNOYZP", false);
}
@Test
public void testActiveOrdering() {
- assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG"); // X
- assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG"); // no change
- assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG"); // Z and X
- assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG"); // Z and X + gap
- verify(mStabilityManager, never()).onEntryReorderSuppressed();
+ when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);
+ assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG", true); // X
+ assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG", true); // no change
+ assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG", true); // Z and X
+ assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG", true); // Z and X + gap
}
@Test
@@ -2062,6 +2132,52 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
@Test
+ public void stableOrderingDisregardedWithSectionChange() {
+ when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);
+ // GIVEN the first sectioner's packages can be changed from run-to-run
+ List<String> mutableSectionerPackages = new ArrayList<>();
+ mutableSectionerPackages.add(PACKAGE_1);
+ mListBuilder.setSectioners(asList(
+ new PackageSectioner(mutableSectionerPackages, null),
+ new PackageSectioner(List.of(PACKAGE_1, PACKAGE_2, PACKAGE_3), null)));
+ mStabilityManager.setAllowEntryReordering(false);
+
+ // WHEN the list is originally built with reordering disabled (and section changes allowed)
+ addNotif(0, PACKAGE_1).setRank(4);
+ addNotif(1, PACKAGE_1).setRank(5);
+ addNotif(2, PACKAGE_2).setRank(1);
+ addNotif(3, PACKAGE_2).setRank(2);
+ addNotif(4, PACKAGE_3).setRank(3);
+ dispatchBuild();
+
+ // VERIFY the order and that entry reordering has not been suppressed
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ notif(2),
+ notif(3),
+ notif(4)
+ );
+ verify(mStabilityManager, never()).onEntryReorderSuppressed();
+
+ // WHEN the first section now claims PACKAGE_3 notifications
+ mutableSectionerPackages.add(PACKAGE_3);
+ dispatchBuild();
+
+ // VERIFY the re-sectioned notification is inserted at #1 of the first section, which
+ // is the correct position based on its rank, rather than #3 in the new section simply
+ // because it was #3 in its previous section.
+ verifyBuiltList(
+ notif(4),
+ notif(0),
+ notif(1),
+ notif(2),
+ notif(3)
+ );
+ verify(mStabilityManager, never()).onEntryReorderSuppressed();
+ }
+
+ @Test
public void testStableChildOrdering() {
// WHEN the list is originally built with reordering disabled
mStabilityManager.setAllowEntryReordering(false);
@@ -2112,6 +2228,85 @@ public class ShadeListBuilderTest extends SysuiTestCase {
);
}
+ @Test
+ public void groupRevertingToSummaryDoesNotRetainStablePositionWithLegacyIndexLogic() {
+ when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(false);
+
+ // GIVEN a notification group is on screen
+ mStabilityManager.setAllowEntryReordering(false);
+
+ // WHEN the list is originally built with reordering disabled (and section changes allowed)
+ addNotif(0, PACKAGE_1).setRank(2);
+ addNotif(1, PACKAGE_1).setRank(3);
+ addGroupSummary(2, PACKAGE_1, "group").setRank(4);
+ addGroupChild(3, PACKAGE_1, "group").setRank(5);
+ addGroupChild(4, PACKAGE_1, "group").setRank(6);
+ dispatchBuild();
+
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ group(
+ summary(2),
+ child(3),
+ child(4)
+ )
+ );
+
+ // WHEN the notification summary rank increases and children removed
+ setNewRank(notif(2).entry, 1);
+ mEntrySet.remove(4);
+ mEntrySet.remove(3);
+ dispatchBuild();
+
+ // VERIFY the summary (incorrectly) moves to the top of the section where it is ranked,
+ // despite visual stability being active
+ verifyBuiltList(
+ notif(2),
+ notif(0),
+ notif(1)
+ );
+ }
+
+ @Test
+ public void groupRevertingToSummaryRetainsStablePosition() {
+ when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(true);
+
+ // GIVEN a notification group is on screen
+ mStabilityManager.setAllowEntryReordering(false);
+
+ // WHEN the list is originally built with reordering disabled (and section changes allowed)
+ addNotif(0, PACKAGE_1).setRank(2);
+ addNotif(1, PACKAGE_1).setRank(3);
+ addGroupSummary(2, PACKAGE_1, "group").setRank(4);
+ addGroupChild(3, PACKAGE_1, "group").setRank(5);
+ addGroupChild(4, PACKAGE_1, "group").setRank(6);
+ dispatchBuild();
+
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ group(
+ summary(2),
+ child(3),
+ child(4)
+ )
+ );
+
+ // WHEN the notification summary rank increases and children removed
+ setNewRank(notif(2).entry, 1);
+ mEntrySet.remove(4);
+ mEntrySet.remove(3);
+ dispatchBuild();
+
+ // VERIFY the summary stays in the same location on rebuild
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ notif(2)
+ );
+ }
+
private static void setNewRank(NotificationEntry entry, int rank) {
entry.setRanking(new RankingBuilder(entry.getRanking()).setRank(rank).build());
}
@@ -2255,26 +2450,35 @@ public class ShadeListBuilderTest extends SysuiTestCase {
return addGroupChildWithTag(index, packageId, groupId, null);
}
- private void assertOrder(String visible, String active, String expected) {
+ private void assertOrder(String visible, String active, String expected,
+ boolean isOrderedCorrectly) {
StringBuilder differenceSb = new StringBuilder();
+ NotifSection section = new NotifSection(mock(NotifSectioner.class), 0);
for (char c : active.toCharArray()) {
if (visible.indexOf(c) < 0) differenceSb.append(c);
}
String difference = differenceSb.toString();
+ int globalIndex = 0;
for (int i = 0; i < visible.length(); i++) {
- addNotif(i, String.valueOf(visible.charAt(i)))
- .setRank(active.indexOf(visible.charAt(i)))
+ final char c = visible.charAt(i);
+ // Skip notifications which aren't active anymore
+ if (!active.contains(String.valueOf(c))) continue;
+ addNotif(globalIndex++, String.valueOf(c))
+ .setRank(active.indexOf(c))
+ .setSection(section)
.setStableIndex(i);
-
}
- for (int i = 0; i < difference.length(); i++) {
- addNotif(i + visible.length(), String.valueOf(difference.charAt(i)))
- .setRank(active.indexOf(difference.charAt(i)))
+ for (char c : difference.toCharArray()) {
+ addNotif(globalIndex++, String.valueOf(c))
+ .setRank(active.indexOf(c))
+ .setSection(section)
.setStableIndex(-1);
}
+ clearInvocations(mStabilityManager);
+
dispatchBuild();
StringBuilder resultSb = new StringBuilder();
for (int i = 0; i < expected.length(); i++) {
@@ -2284,6 +2488,9 @@ public class ShadeListBuilderTest extends SysuiTestCase {
assertEquals("visible [" + visible + "] active [" + active + "]",
expected, resultSb.toString());
mEntrySet.clear();
+
+ verify(mStabilityManager, isOrderedCorrectly ? never() : times(1))
+ .onEntryReorderSuppressed();
}
private int nextId(String packageName) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 340bc96f80c2..3ff7639e9262 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -674,7 +674,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
@Test
fun testOnRankingApplied_newEntryShouldAlert() {
// GIVEN that mEntry has never interrupted in the past, and now should
+ // and is new enough to do so
assertFalse(mEntry.hasInterrupted())
+ mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis())
setShouldHeadsUp(mEntry)
whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
@@ -690,8 +692,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
@Test
fun testOnRankingApplied_alreadyAlertedEntryShouldNotAlertAgain() {
- // GIVEN that mEntry has alerted in the past
+ // GIVEN that mEntry has alerted in the past, even if it's new
mEntry.setInterruption()
+ mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis())
setShouldHeadsUp(mEntry)
whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
@@ -725,6 +728,27 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
verify(mHeadsUpManager).showNotification(mEntry)
}
+ @Test
+ fun testOnRankingApplied_entryUpdatedButTooOld() {
+ // GIVEN that mEntry is added in a state where it should not HUN
+ setShouldHeadsUp(mEntry, false)
+ mCollectionListener.onEntryAdded(mEntry)
+
+ // and it was actually added 10s ago
+ mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis() - 10000)
+
+ // WHEN it is updated to HUN and then a ranking update occurs
+ setShouldHeadsUp(mEntry)
+ whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+ mCollectionListener.onRankingApplied()
+ mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+ mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+ // THEN the notification is never bound or shown
+ verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+ verify(mHeadsUpManager, never()).showNotification(any())
+ }
+
private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
whenever(mNotificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
index e1e5051751bb..590c902ba687 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
@@ -35,7 +35,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index f4adf6927e31..b6b0b7738997 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -181,7 +181,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testInflatesNewNotification() {
// WHEN there is a new notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
// THEN we inflate it
@@ -194,7 +194,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testRebindsInflatedNotificationsOnUpdate() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -213,7 +213,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testEntrySmartReplyAdditionWillRebindViews() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -232,7 +232,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testEntryChangedToMinimizedSectionWillRebindViews() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
assertFalse(mParamsCaptor.getValue().isLowPriority());
@@ -254,7 +254,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
public void testMinimizedEntryMovedIntoGroupWillRebindViews() {
// GIVEN an inflated, minimized notification
setSectionIsLowPriority(true);
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any());
assertTrue(mParamsCaptor.getValue().isLowPriority());
@@ -275,7 +275,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testEntryRankChangeWillNotRebindViews() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -294,7 +294,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Test
public void testDoesntFilterInflatedNotifs() {
// GIVEN an inflated notification
- mCollectionListener.onEntryAdded(mEntry);
+ mCollectionListener.onEntryInit(mEntry);
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
verify(mNotifInflater).inflateViews(eq(mEntry), any(), any());
mNotifInflater.invokeInflateCallbackForEntry(mEntry);
@@ -330,9 +330,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
mCollectionListener.onEntryInit(entry);
}
- mCollectionListener.onEntryAdded(summary);
+ mCollectionListener.onEntryInit(summary);
for (NotificationEntry entry : children) {
- mCollectionListener.onEntryAdded(entry);
+ mCollectionListener.onEntryInit(entry);
}
mBeforeFilterListener.onBeforeFinalizeFilter(List.of(groupEntry));
@@ -393,6 +393,70 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
}
@Test
+ public void testNullGroupSummary() {
+ // GIVEN a newly-posted group with a summary and two children
+ final GroupEntry group = new GroupEntryBuilder()
+ .setCreationTime(400)
+ .setSummary(getNotificationEntryBuilder().setId(1).build())
+ .addChild(getNotificationEntryBuilder().setId(2).build())
+ .addChild(getNotificationEntryBuilder().setId(3).build())
+ .build();
+ fireAddEvents(List.of(group));
+ final NotificationEntry child0 = group.getChildren().get(0);
+ final NotificationEntry child1 = group.getChildren().get(1);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+ // WHEN the summary is pruned
+ new GroupEntryBuilder()
+ .setCreationTime(400)
+ .addChild(child0)
+ .addChild(child1)
+ .build();
+
+ // WHEN all of the children (but not the summary) finish inflating
+ mNotifInflater.invokeInflateCallbackForEntry(child0);
+ mNotifInflater.invokeInflateCallbackForEntry(child1);
+
+ // THEN the entire group is not filtered out
+ assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
+ assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
+ }
+
+ @Test
+ public void testPartiallyInflatedGroupsAreNotFilteredOutIfSummaryReinflate() {
+ // GIVEN a newly-posted group with a summary and two children
+ final String groupKey = "test_reinflate_group";
+ final int summaryId = 1;
+ final GroupEntry group = new GroupEntryBuilder()
+ .setKey(groupKey)
+ .setCreationTime(400)
+ .setSummary(getNotificationEntryBuilder().setId(summaryId).setImportance(1).build())
+ .addChild(getNotificationEntryBuilder().setId(2).build())
+ .addChild(getNotificationEntryBuilder().setId(3).build())
+ .build();
+ fireAddEvents(List.of(group));
+ final NotificationEntry summary = group.getSummary();
+ final NotificationEntry child0 = group.getChildren().get(0);
+ final NotificationEntry child1 = group.getChildren().get(1);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+ // WHEN all of the children (but not the summary) finish inflating
+ mNotifInflater.invokeInflateCallbackForEntry(child0);
+ mNotifInflater.invokeInflateCallbackForEntry(child1);
+ mNotifInflater.invokeInflateCallbackForEntry(summary);
+
+ // WHEN the summary is updated and starts re-inflating
+ summary.setRanking(new RankingBuilder(summary.getRanking()).setImportance(4).build());
+ fireUpdateEvents(summary);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+ // THEN the entire group is still not filtered out
+ assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));
+ assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
+ assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
+ }
+
+ @Test
public void testCompletedInflatedGroupsAreReleased() {
// GIVEN a newly-posted group with a summary and two children
final GroupEntry group = new GroupEntryBuilder()
@@ -412,7 +476,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
mNotifInflater.invokeInflateCallbackForEntry(child1);
mNotifInflater.invokeInflateCallbackForEntry(summary);
- // THEN the entire group is still filtered out
+ // THEN the entire group is no longer filtered out
assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));
assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
@@ -494,7 +558,11 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
private void fireAddEvents(NotificationEntry entry) {
mCollectionListener.onEntryInit(entry);
- mCollectionListener.onEntryAdded(entry);
+ mCollectionListener.onEntryInit(entry);
+ }
+
+ private void fireUpdateEvents(NotificationEntry entry) {
+ mCollectionListener.onEntryUpdated(entry);
}
private static final String TEST_MESSAGE = "TEST_MESSAGE";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
new file mode 100644
index 000000000000..1cdd023dd01c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.listbuilder
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.util.Log
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class SemiStableSortTest : SysuiTestCase() {
+
+ var shuffleInput: Boolean = false
+ var testStabilizeTo: Boolean = false
+ var sorter: SemiStableSort? = null
+
+ @Before
+ fun setUp() {
+ shuffleInput = false
+ sorter = null
+ }
+
+ private fun stringStabilizeTo(
+ stableOrder: String,
+ activeOrder: String,
+ ): Pair<String, Boolean> {
+ val actives = activeOrder.toMutableList()
+ val result = mutableListOf<Char>()
+ return (sorter ?: SemiStableSort())
+ .stabilizeTo(
+ actives,
+ { ch -> stableOrder.indexOf(ch).takeIf { it >= 0 } },
+ result,
+ )
+ .let { ordered -> result.joinToString("") to ordered }
+ }
+
+ private fun stringSort(
+ stableOrder: String,
+ activeOrder: String,
+ ): Pair<String, Boolean> {
+ val actives = activeOrder.toMutableList()
+ if (shuffleInput) {
+ actives.shuffle()
+ }
+ return (sorter ?: SemiStableSort())
+ .sort(
+ actives,
+ { ch -> stableOrder.indexOf(ch).takeIf { it >= 0 } },
+ compareBy { activeOrder.indexOf(it) },
+ )
+ .let { ordered -> actives.joinToString("") to ordered }
+ }
+
+ private fun testCase(
+ stableOrder: String,
+ activeOrder: String,
+ expected: String,
+ expectOrdered: Boolean,
+ ) {
+ val (mergeResult, ordered) =
+ if (testStabilizeTo) stringStabilizeTo(stableOrder, activeOrder)
+ else stringSort(stableOrder, activeOrder)
+ val resultPass = expected == mergeResult
+ val orderedPass = ordered == expectOrdered
+ val pass = resultPass && orderedPass
+ val resultSuffix =
+ if (resultPass) "result=$expected" else "expected=$expected got=$mergeResult"
+ val orderedSuffix =
+ if (orderedPass) "ordered=$ordered" else "expected ordered to be $expectOrdered"
+ val readableResult = "stable=$stableOrder active=$activeOrder $resultSuffix $orderedSuffix"
+ Log.d("SemiStableSortTest", "${if (pass) "PASS" else "FAIL"}: $readableResult")
+ if (!pass) {
+ throw AssertionError("Test case failed: $readableResult")
+ }
+ }
+
+ private fun runAllTestCases() {
+ // No input or output
+ testCase("", "", "", true)
+ // Remove everything
+ testCase("ABCDEFG", "", "", true)
+ // Literally no changes
+ testCase("ABCDEFG", "ABCDEFG", "ABCDEFG", true)
+
+ // No stable order
+ testCase("", "ABCDEFG", "ABCDEFG", true)
+
+ // F moved after A, and...
+ testCase("ABCDEFG", "AFBCDEG", "ABCDEFG", false) // No other changes
+ testCase("ABCDEFG", "AXFBCDEG", "AXBCDEFG", false) // Insert X before F
+ testCase("ABCDEFG", "AFXBCDEG", "AXBCDEFG", false) // Insert X after F
+ testCase("ABCDEFG", "AFBCDEXG", "ABCDEFXG", false) // Insert X where F was
+
+ // B moved after F, and...
+ testCase("ABCDEFG", "ACDEFBG", "ABCDEFG", false) // No other changes
+ testCase("ABCDEFG", "ACDEFXBG", "ABCDEFXG", false) // Insert X before B
+ testCase("ABCDEFG", "ACDEFBXG", "ABCDEFXG", false) // Insert X after B
+ testCase("ABCDEFG", "AXCDEFBG", "AXBCDEFG", false) // Insert X where B was
+
+ // Swap F and B, and...
+ testCase("ABCDEFG", "AFCDEBG", "ABCDEFG", false) // No other changes
+ testCase("ABCDEFG", "AXFCDEBG", "AXBCDEFG", false) // Insert X before F
+ testCase("ABCDEFG", "AFXCDEBG", "AXBCDEFG", false) // Insert X after F
+ testCase("ABCDEFG", "AFCXDEBG", "AXBCDEFG", false) // Insert X between CD (Alt: ABCXDEFG)
+ testCase("ABCDEFG", "AFCDXEBG", "ABCDXEFG", false) // Insert X between DE (Alt: ABCDEFXG)
+ testCase("ABCDEFG", "AFCDEXBG", "ABCDEFXG", false) // Insert X before B
+ testCase("ABCDEFG", "AFCDEBXG", "ABCDEFXG", false) // Insert X after B
+
+ // Remove a bunch of entries at once
+ testCase("ABCDEFGHIJKL", "ACEGHI", "ACEGHI", true)
+
+ // Remove a bunch of entries and scramble
+ testCase("ABCDEFGHIJKL", "GCEHAI", "ACEGHI", false)
+
+ // Add a bunch of entries at once
+ testCase("ABCDEFG", "AVBWCXDYZEFG", "AVBWCXDYZEFG", true)
+
+ // Add a bunch of entries and reverse originals
+ // NOTE: Some of these don't have obviously correct answers
+ testCase("ABCDEFG", "GFEBCDAVWXYZ", "ABCDEFGVWXYZ", false) // appended
+ testCase("ABCDEFG", "VWXYZGFEBCDA", "VWXYZABCDEFG", false) // prepended
+ testCase("ABCDEFG", "GFEBVWXYZCDA", "ABCDEFGVWXYZ", false) // closer to back: append
+ testCase("ABCDEFG", "GFEVWXYZBCDA", "VWXYZABCDEFG", false) // closer to front: prepend
+ testCase("ABCDEFG", "GFEVWBXYZCDA", "VWABCDEFGXYZ", false) // split new entries
+
+ // Swap 2 pairs ("*BC*NO*"->"*NO*CB*"), remove EG, add UVWXYZ throughout
+ testCase("ABCDEFGHIJKLMNOP", "AUNOVDFHWXIJKLMYCBZP", "AUVBCDFHWXIJKLMNOYZP", false)
+ }
+
+ @Test
+ fun testSort() {
+ testStabilizeTo = false
+ shuffleInput = false
+ sorter = null
+ runAllTestCases()
+ }
+
+ @Test
+ fun testSortWithSingleInstance() {
+ testStabilizeTo = false
+ shuffleInput = false
+ sorter = SemiStableSort()
+ runAllTestCases()
+ }
+
+ @Test
+ fun testSortWithShuffledInput() {
+ testStabilizeTo = false
+ shuffleInput = true
+ sorter = null
+ runAllTestCases()
+ }
+
+ @Test
+ fun testStabilizeTo() {
+ testStabilizeTo = true
+ sorter = null
+ runAllTestCases()
+ }
+
+ @Test
+ fun testStabilizeToWithSingleInstance() {
+ testStabilizeTo = true
+ sorter = SemiStableSort()
+ runAllTestCases()
+ }
+
+ @Test
+ fun testIsSorted() {
+ val intCmp = Comparator<Int> { x, y -> Integer.compare(x, y) }
+ SemiStableSort.apply {
+ assertTrue(emptyList<Int>().isSorted(intCmp))
+ assertTrue(listOf(1).isSorted(intCmp))
+ assertTrue(listOf(1, 2).isSorted(intCmp))
+ assertTrue(listOf(1, 2, 3).isSorted(intCmp))
+ assertTrue(listOf(1, 2, 3, 4).isSorted(intCmp))
+ assertTrue(listOf(1, 2, 3, 4, 5).isSorted(intCmp))
+ assertTrue(listOf(1, 1, 1, 1, 1).isSorted(intCmp))
+ assertTrue(listOf(1, 1, 2, 2, 3, 3).isSorted(intCmp))
+ assertFalse(listOf(2, 1).isSorted(intCmp))
+ assertFalse(listOf(2, 1, 2).isSorted(intCmp))
+ assertFalse(listOf(1, 2, 1).isSorted(intCmp))
+ assertFalse(listOf(1, 2, 3, 2, 5).isSorted(intCmp))
+ assertFalse(listOf(5, 2, 3, 4, 5).isSorted(intCmp))
+ assertFalse(listOf(1, 2, 3, 4, 1).isSorted(intCmp))
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
new file mode 100644
index 000000000000..20369546d68a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.listbuilder
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper.getContiguousSubLists
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class ShadeListBuilderHelperTest : SysuiTestCase() {
+
+ @Test
+ fun testGetContiguousSubLists() {
+ assertThat(getContiguousSubLists("AAAAAA".toList()) { it })
+ .containsExactly(
+ listOf('A', 'A', 'A', 'A', 'A', 'A'),
+ )
+ .inOrder()
+ assertThat(getContiguousSubLists("AAABBB".toList()) { it })
+ .containsExactly(
+ listOf('A', 'A', 'A'),
+ listOf('B', 'B', 'B'),
+ )
+ .inOrder()
+ assertThat(getContiguousSubLists("AAABAA".toList()) { it })
+ .containsExactly(
+ listOf('A', 'A', 'A'),
+ listOf('B'),
+ listOf('A', 'A'),
+ )
+ .inOrder()
+ assertThat(getContiguousSubLists("AAABAA".toList(), minLength = 2) { it })
+ .containsExactly(
+ listOf('A', 'A', 'A'),
+ listOf('A', 'A'),
+ )
+ .inOrder()
+ assertThat(getContiguousSubLists("AAABBBBCCDEEE".toList()) { it })
+ .containsExactly(
+ listOf('A', 'A', 'A'),
+ listOf('B', 'B', 'B', 'B'),
+ listOf('C', 'C'),
+ listOf('D'),
+ listOf('E', 'E', 'E'),
+ )
+ .inOrder()
+ assertThat(getContiguousSubLists("AAABBBBCCDEEE".toList(), minLength = 2) { it })
+ .containsExactly(
+ listOf('A', 'A', 'A'),
+ listOf('B', 'B', 'B', 'B'),
+ listOf('C', 'C'),
+ listOf('E', 'E', 'E'),
+ )
+ .inOrder()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index d59cc54dfe98..8b7b4dea155f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -29,6 +29,7 @@ import static com.android.systemui.statusbar.notification.collection.EntryUtilKt
import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.argThat;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
@@ -305,15 +306,59 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
}
@Test
- public void hideSilentNotificationsPerUserSetting() {
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ public void hideSilentOnLockscreenSetting() {
+ // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen
+ setupUnfilteredState(mEntry);
mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+
+ // WHEN the show silent notifs on lockscreen setting is false
mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+
+ // WHEN the notification is not high priority and not ambient
+ mEntry = new NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_LOW)
+ .build();
+ when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false);
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void showSilentOnLockscreenSetting() {
+ // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen
+ setupUnfilteredState(mEntry);
+ mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+
+ // WHEN the show silent notifs on lockscreen setting is true
+ mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true);
+
+ // WHEN the notification is not high priority and not ambient
+ mEntry = new NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_LOW)
+ .build();
+ when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
+
+ // THEN do not filter out the entry
+ assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void defaultSilentOnLockscreenSettingIsHide() {
+ // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen
+ setupUnfilteredState(mEntry);
+ mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+
+ // WHEN the notification is not high priority and not ambient
mEntry = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
.setImportance(IMPORTANCE_LOW)
.build();
when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false);
+
+ // WhHEN the show silent notifs on lockscreen setting is unset
+ assertNull(mFakeSettings.getString(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS));
+
assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
}
@@ -431,25 +476,6 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
}
@Test
- public void showSilentOnLockscreenSetting() {
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState(mEntry);
-
- // WHEN the notification is not high priority and not ambient
- mEntry.setRanking(new RankingBuilder()
- .setKey(mEntry.getKey())
- .setImportance(IMPORTANCE_LOW)
- .build());
- when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
-
- // WHEN the show silent notifs on lockscreen setting is true
- mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true);
-
- // THEN do not filter out the entry
- assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
- }
-
- @Test
public void notificationVisibilityPublic() {
// GIVEN a VISIBILITY_PUBLIC notification
NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 46f630b7db63..ea311da3e20b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -51,12 +51,14 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -97,6 +99,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
NotifPipelineFlags mFlags;
@Mock
KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
+ UiEventLoggerFake mUiEventLoggerFake;
@Mock
PendingIntent mPendingIntent;
@@ -107,6 +110,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
+ mUiEventLoggerFake = new UiEventLoggerFake();
+
mNotifInterruptionStateProvider =
new NotificationInterruptStateProviderImpl(
mContext.getContentResolver(),
@@ -120,7 +125,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
mLogger,
mMockHandler,
mFlags,
- mKeyguardNotificationVisibilityProvider);
+ mKeyguardNotificationVisibilityProvider,
+ mUiEventLoggerFake);
mNotifInterruptionStateProvider.mUseHeadsUp = true;
}
@@ -442,6 +448,13 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
verify(mLogger, never()).logNoFullscreen(any(), any());
verify(mLogger).logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
verify(mLogger, never()).logFullscreen(any(), any());
+
+ assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+ UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+ assertThat(fakeUiEvent.eventId).isEqualTo(
+ NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR.getId());
+ assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+ assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
}
@Test
@@ -600,6 +613,13 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
verify(mLogger, never()).logNoFullscreen(any(), any());
verify(mLogger).logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
verify(mLogger, never()).logFullscreen(any(), any());
+
+ assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+ UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+ assertThat(fakeUiEvent.eventId).isEqualTo(
+ NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD.getId());
+ assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+ assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
index 16e2441c556b..f69839b7087c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
@@ -28,30 +28,21 @@ import android.widget.RemoteViews
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.NotificationUtils
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
-class NotificationMemoryMonitorTest : SysuiTestCase() {
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- }
+class NotificationMemoryMeterTest : SysuiTestCase() {
@Test
fun currentNotificationMemoryUse_plainNotification() {
val notification = createBasicNotification().build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -69,8 +60,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
fun currentNotificationMemoryUse_plainNotification_dontDoubleCountSameBitmap() {
val icon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
val notification = createBasicNotification().setLargeIcon(icon).setSmallIcon(icon).build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -92,8 +83,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
RemoteViews(context.packageName, android.R.layout.list_content)
)
.build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -112,8 +103,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
val dataIcon = Icon.createWithData(ByteArray(444444), 0, 444444)
val notification =
createBasicNotification().setLargeIcon(dataIcon).setSmallIcon(dataIcon).build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = 444444,
@@ -141,8 +132,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
.bigLargeIcon(bigPictureIcon)
)
.build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -167,8 +158,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
createBasicNotification()
.setStyle(Notification.CallStyle.forIncomingCall(person, fakeIntent, fakeIntent))
.build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -203,8 +194,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
.addHistoricMessage(historicMessage)
)
.build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -225,8 +216,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
val carIcon = Bitmap.createBitmap(432, 322, Bitmap.Config.ARGB_8888)
val extender = Notification.CarExtender().setLargeIcon(carIcon)
val notification = createBasicNotification().extend(extender).build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -246,8 +237,8 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
val wearBackground = Bitmap.createBitmap(443, 433, Bitmap.Config.ARGB_8888)
val wearExtender = Notification.WearableExtender().setBackground(wearBackground)
val notification = createBasicNotification().extend(tvExtender).extend(wearExtender).build()
- val nmm = createNMMWithNotifications(listOf(notification))
- val memoryUse = getUseObject(nmm.currentNotificationMemoryUse())
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(createNotificationEntry(notification))
assertNotificationObjectSizes(
memoryUse = memoryUse,
smallIcon = notification.smallIcon.bitmap.allocationByteCount,
@@ -283,10 +274,10 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
extender: Int,
style: String?,
styleIcon: Int,
- hasCustomView: Boolean
+ hasCustomView: Boolean,
) {
assertThat(memoryUse.packageName).isEqualTo("test_pkg")
- assertThat(memoryUse.notificationId)
+ assertThat(memoryUse.notificationKey)
.isEqualTo(NotificationUtils.logKey("0|test_pkg|0|test|0"))
assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
@@ -301,21 +292,14 @@ class NotificationMemoryMonitorTest : SysuiTestCase() {
}
private fun getUseObject(
- singleItemUseList: List<NotificationMemoryUsage>
+ singleItemUseList: List<NotificationMemoryUsage>,
): NotificationMemoryUsage {
assertThat(singleItemUseList).hasSize(1)
return singleItemUseList[0]
}
- private fun createNMMWithNotifications(
- notifications: List<Notification>
- ): NotificationMemoryMonitor {
- val notifPipeline: NotifPipeline = mock()
- val notificationEntries =
- notifications.map { n ->
- NotificationEntryBuilder().setTag("test").setNotification(n).build()
- }
- whenever(notifPipeline.allNotifs).thenReturn(notificationEntries)
- return NotificationMemoryMonitor(notifPipeline, mock())
- }
+ private fun createNotificationEntry(
+ notification: Notification,
+ ): NotificationEntry =
+ NotificationEntryBuilder().setTag("test").setNotification(notification).build()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
new file mode 100644
index 000000000000..3a16fb33388b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
@@ -0,0 +1,148 @@
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.widget.RemoteViews
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.tests.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NotificationMemoryViewWalkerTest : SysuiTestCase() {
+
+ private lateinit var testHelper: NotificationTestHelper
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ testHelper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ }
+
+ @Test
+ fun testViewWalker_nullRow_returnsEmptyView() {
+ val result = NotificationMemoryViewWalker.getViewUsage(null)
+ assertThat(result).isNotNull()
+ assertThat(result).isEmpty()
+ }
+
+ @Test
+ fun testViewWalker_plainNotification() {
+ val row = testHelper.createRow()
+ val result = NotificationMemoryViewWalker.getViewUsage(row)
+ assertThat(result).hasSize(5)
+ assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result)
+ .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result)
+ .contains(NotificationViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result)
+ .contains(NotificationViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result)
+ .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
+ }
+
+ @Test
+ fun testViewWalker_bigPictureNotification() {
+ val bigPicture = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
+ val largeIcon = Icon.createWithBitmap(Bitmap.createBitmap(60, 60, Bitmap.Config.ARGB_8888))
+ val row =
+ testHelper.createRow(
+ Notification.Builder(mContext)
+ .setContentText("Test")
+ .setContentTitle("title")
+ .setSmallIcon(icon)
+ .setLargeIcon(largeIcon)
+ .setStyle(Notification.BigPictureStyle().bigPicture(bigPicture))
+ .build()
+ )
+ val result = NotificationMemoryViewWalker.getViewUsage(row)
+ assertThat(result).hasSize(5)
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.PRIVATE_EXPANDED_VIEW,
+ icon.bitmap.allocationByteCount,
+ largeIcon.bitmap.allocationByteCount,
+ 0,
+ bigPicture.allocationByteCount,
+ 0,
+ bigPicture.allocationByteCount +
+ icon.bitmap.allocationByteCount +
+ largeIcon.bitmap.allocationByteCount
+ )
+ )
+
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.PRIVATE_CONTRACTED_VIEW,
+ icon.bitmap.allocationByteCount,
+ largeIcon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ icon.bitmap.allocationByteCount + largeIcon.bitmap.allocationByteCount
+ )
+ )
+ // Due to deduplication, this should all be 0.
+ assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+ }
+
+ @Test
+ fun testViewWalker_customView() {
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
+ val bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
+
+ val views = RemoteViews(mContext.packageName, R.layout.custom_view_dark)
+ views.setImageViewBitmap(R.id.custom_view_dark_image, bitmap)
+ val row =
+ testHelper.createRow(
+ Notification.Builder(mContext)
+ .setContentText("Test")
+ .setContentTitle("title")
+ .setSmallIcon(icon)
+ .setCustomContentView(views)
+ .setCustomBigContentView(views)
+ .build()
+ )
+ val result = NotificationMemoryViewWalker.getViewUsage(row)
+ assertThat(result).hasSize(5)
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.PRIVATE_CONTRACTED_VIEW,
+ icon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ bitmap.allocationByteCount,
+ bitmap.allocationByteCount + icon.bitmap.allocationByteCount
+ )
+ )
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.PRIVATE_EXPANDED_VIEW,
+ icon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ bitmap.allocationByteCount,
+ bitmap.allocationByteCount + icon.bitmap.allocationByteCount
+ )
+ )
+ // Due to deduplication, this should all be 0.
+ assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 8375e7cceb28..5394d88ad103 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -51,7 +51,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 9abdeb900c67..421f918a135e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -52,7 +52,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 11798a7a4f96..87f4c323b7cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -361,6 +361,22 @@ class AmbientStateTest : SysuiTestCase() {
assertThat(sut.isOnKeyguard).isFalse()
}
// endregion
+
+ // region mIsClosing
+ @Test
+ fun isClosing_whenShadeClosing_shouldReturnTrue() {
+ sut.setIsClosing(true)
+
+ assertThat(sut.isClosing).isTrue()
+ }
+
+ @Test
+ fun isClosing_whenShadeFinishClosing_shouldReturnFalse() {
+ sut.setIsClosing(false)
+
+ assertThat(sut.isClosing).isFalse()
+ }
+ // endregion
}
// region Arrange helper methods.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index a95a49c31adf..8c8b64424814 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -147,8 +147,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mFirst, mSecond),
createSection(null, null)
});
- Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mSecond.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(mSmallRadiusRatio, mSecond.getTopRoundness(), 0.0f);
}
@Test
@@ -170,13 +170,13 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
when(testHelper.getStatusBarStateController().isDozing()).thenReturn(true);
row.setHeadsUp(true);
mRoundnessManager.updateView(entry.getRow(), false);
- Assert.assertEquals(1f, row.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(1f, row.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(1f, row.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(1f, row.getTopRoundness(), 0.0f);
row.setHeadsUp(false);
mRoundnessManager.updateView(entry.getRow(), false);
- Assert.assertEquals(mSmallRadiusRatio, row.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(mSmallRadiusRatio, row.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(mSmallRadiusRatio, row.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(mSmallRadiusRatio, row.getTopRoundness(), 0.0f);
}
@Test
@@ -185,8 +185,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mFirst, mFirst),
createSection(null, mSecond)
});
- Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mSecond.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(mSmallRadiusRatio, mSecond.getTopRoundness(), 0.0f);
}
@Test
@@ -195,8 +195,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mFirst, mFirst),
createSection(mSecond, null)
});
- Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(1.0f, mSecond.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(mSmallRadiusRatio, mSecond.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mSecond.getTopRoundness(), 0.0f);
}
@Test
@@ -205,8 +205,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mFirst, null),
createSection(null, null)
});
- Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
}
@Test
@@ -215,8 +215,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond),
createSection(null, null)
});
- Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(mSmallRadiusRatio, mFirst.getTopRoundness(), 0.0f);
}
@Test
@@ -226,8 +226,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond),
createSection(null, null)
});
- Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
}
@Test
@@ -238,8 +238,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond),
createSection(null, null)
});
- Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
}
@Test
@@ -250,8 +250,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond),
createSection(null, null)
});
- Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
}
@Test
@@ -262,8 +262,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond),
createSection(null, null)
});
- Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(mSmallRadiusRatio, mFirst.getTopRoundness(), 0.0f);
}
@Test
@@ -274,8 +274,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond),
createSection(null, null)
});
- Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
}
@Test
@@ -286,8 +286,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(mSecond, mSecond),
createSection(null, null)
});
- Assert.assertEquals(0.5f, mFirst.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(0.5f, mFirst.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(0.5f, mFirst.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(0.5f, mFirst.getTopRoundness(), 0.0f);
}
@Test
@@ -298,8 +298,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
createSection(null, null)
});
mFirst.setHeadsUpAnimatingAway(true);
- Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mFirst.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(1.0f, mFirst.getTopRoundness(), 0.0f);
}
@@ -312,8 +312,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
});
mFirst.setHeadsUpAnimatingAway(true);
mFirst.setHeadsUpAnimatingAway(false);
- Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f);
- Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f);
+ Assert.assertEquals(mSmallRadiusRatio, mFirst.getBottomRoundness(), 0.0f);
+ Assert.assertEquals(mSmallRadiusRatio, mFirst.getTopRoundness(), 0.0f);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 9d848e87b0a0..ecc02246ea1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -30,7 +30,7 @@ import android.view.ViewGroup;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
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 1c9b0be0185a..90061b078afe 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
@@ -46,7 +46,7 @@ import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -129,6 +129,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Mock private ShadeTransitionController mShadeTransitionController;
@Mock private FeatureFlags mFeatureFlags;
+ @Mock private NotificationTargetsHelper mNotificationTargetsHelper;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -177,7 +178,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mStackLogger,
mLogger,
mNotificationStackSizeCalculator,
- mFeatureFlags
+ mFeatureFlags,
+ mNotificationTargetsHelper
);
when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 43530365360b..91aecd8cf753 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -163,7 +163,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setCentralSurfaces(mCentralSurfaces);
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
- when(mStackScrollLayoutController.getNoticationRoundessManager())
+ when(mStackScrollLayoutController.getNotificationRoundnessManager())
.thenReturn(mNotificationRoundnessManager);
mStackScroller.setController(mStackScrollLayoutController);
@@ -728,6 +728,57 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());
}
+ @Test
+ public void testSetOwnScrollY_shadeNotClosing_scrollYChanges() {
+ // Given: shade is not closing, scrollY is 0
+ mAmbientState.setScrollY(0);
+ assertEquals(0, mAmbientState.getScrollY());
+ mAmbientState.setIsClosing(false);
+
+ // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1
+ mStackScroller.setOwnScrollY(1);
+
+ // Then: scrollY should be set to 1
+ assertEquals(1, mAmbientState.getScrollY());
+
+ // Reset scrollY back to 0 to avoid interfering with other tests
+ mStackScroller.setOwnScrollY(0);
+ assertEquals(0, mAmbientState.getScrollY());
+ }
+
+ @Test
+ public void testSetOwnScrollY_shadeClosing_scrollYDoesNotChange() {
+ // Given: shade is closing, scrollY is 0
+ mAmbientState.setScrollY(0);
+ assertEquals(0, mAmbientState.getScrollY());
+ mAmbientState.setIsClosing(true);
+
+ // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1
+ mStackScroller.setOwnScrollY(1);
+
+ // Then: scrollY should not change, it should still be 0
+ assertEquals(0, mAmbientState.getScrollY());
+
+ // Reset scrollY and mAmbientState.mIsClosing to avoid interfering with other tests
+ mAmbientState.setIsClosing(false);
+ mStackScroller.setOwnScrollY(0);
+ assertEquals(0, mAmbientState.getScrollY());
+ }
+
+ @Test
+ public void onShadeFlingClosingEnd_scrollYShouldBeSetToZero() {
+ // Given: mAmbientState.mIsClosing is set to be true
+ // mIsExpanded is set to be false
+ mAmbientState.setIsClosing(true);
+ mStackScroller.setIsExpanded(false);
+
+ // When: onExpansionStopped is called
+ mStackScroller.onExpansionStopped();
+
+ // Then: mAmbientState.scrollY should be set to be 0
+ assertEquals(mAmbientState.getScrollY(), 0);
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
new file mode 100644
index 000000000000..a2e92305bf27
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
@@ -0,0 +1,107 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.util.mockito.mock
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests for {@link NotificationTargetsHelper}. */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationTargetsHelperTest : SysuiTestCase() {
+ lateinit var notificationTestHelper: NotificationTestHelper
+ private val sectionsManager: NotificationSectionsManager = mock()
+ private val stackScrollLayout: NotificationStackScrollLayout = mock()
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ notificationTestHelper =
+ NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ }
+
+ private fun notificationTargetsHelper(
+ notificationGroupCorner: Boolean = true,
+ ) =
+ NotificationTargetsHelper(
+ FakeFeatureFlags().apply {
+ set(Flags.NOTIFICATION_GROUP_CORNER, notificationGroupCorner)
+ }
+ )
+
+ @Test
+ fun targetsForFirstNotificationInGroup() {
+ val children = notificationTestHelper.createGroup(3).childrenContainer
+ val swiped = children.attachedChildren[0]
+
+ val actual =
+ notificationTargetsHelper()
+ .findRoundableTargets(
+ viewSwiped = swiped,
+ stackScrollLayout = stackScrollLayout,
+ sectionsManager = sectionsManager,
+ )
+
+ val expected =
+ RoundableTargets(
+ before = children.notificationHeaderWrapper, // group header
+ swiped = swiped,
+ after = children.attachedChildren[1],
+ )
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun targetsForMiddleNotificationInGroup() {
+ val children = notificationTestHelper.createGroup(3).childrenContainer
+ val swiped = children.attachedChildren[1]
+
+ val actual =
+ notificationTargetsHelper()
+ .findRoundableTargets(
+ viewSwiped = swiped,
+ stackScrollLayout = stackScrollLayout,
+ sectionsManager = sectionsManager,
+ )
+
+ val expected =
+ RoundableTargets(
+ before = children.attachedChildren[0],
+ swiped = swiped,
+ after = children.attachedChildren[2],
+ )
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ fun targetsForLastNotificationInGroup() {
+ val children = notificationTestHelper.createGroup(3).childrenContainer
+ val swiped = children.attachedChildren[2]
+
+ val actual =
+ notificationTargetsHelper()
+ .findRoundableTargets(
+ viewSwiped = swiped,
+ stackScrollLayout = stackScrollLayout,
+ sectionsManager = sectionsManager,
+ )
+
+ val expected =
+ RoundableTargets(
+ before = children.attachedChildren[1],
+ swiped = swiped,
+ after = null,
+ )
+ assertEquals(expected, actual)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index ad497a2ec1e1..6de8bd5f3670 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -80,6 +80,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
@@ -98,7 +99,8 @@ import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -271,7 +273,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private OngoingCallController mOngoingCallController;
@Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@Mock private LockscreenShadeTransitionController mLockscreenTransitionController;
- @Mock private FeatureFlags mFeatureFlags;
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@Mock private WallpaperManager mWallpaperManager;
@Mock private IWallpaperManager mIWallpaperManager;
@@ -296,9 +297,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
- private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
- private FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
- private InitController mInitController = new InitController();
+ private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
+ private final FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
+ private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+ private final InitController mInitController = new InitController();
private final DumpManager mDumpManager = new DumpManager();
@Before
@@ -322,7 +324,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mock(NotificationInterruptLogger.class),
new Handler(TestableLooper.get(this).getLooper()),
mock(NotifPipelineFlags.class),
- mock(KeyguardNotificationVisibilityProvider.class));
+ mock(KeyguardNotificationVisibilityProvider.class),
+ mock(UiEventLogger.class));
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -1017,6 +1020,60 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
}
@Test
+ public void collapseShade_callsAnimateCollapsePanels_whenExpanded() {
+ // GIVEN the shade is expanded
+ mCentralSurfaces.setPanelExpanded(true);
+ mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
+
+ // WHEN collapseShade is called
+ mCentralSurfaces.collapseShade();
+
+ // VERIFY that animateCollapsePanels is called
+ verify(mShadeController).animateCollapsePanels();
+ }
+
+ @Test
+ public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() {
+ // GIVEN the shade is collapsed
+ mCentralSurfaces.setPanelExpanded(false);
+ mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
+
+ // WHEN collapseShade is called
+ mCentralSurfaces.collapseShade();
+
+ // VERIFY that animateCollapsePanels is NOT called
+ verify(mShadeController, never()).animateCollapsePanels();
+ }
+
+ @Test
+ public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() {
+ // GIVEN the shade is expanded & flag enabled
+ mCentralSurfaces.setPanelExpanded(true);
+ mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
+ mFeatureFlags.set(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, false);
+
+ // WHEN collapseShadeForBugreport is called
+ mCentralSurfaces.collapseShadeForBugreport();
+
+ // VERIFY that animateCollapsePanels is called
+ verify(mShadeController).animateCollapsePanels();
+ }
+
+ @Test
+ public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() {
+ // GIVEN the shade is expanded & flag enabled
+ mCentralSurfaces.setPanelExpanded(true);
+ mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
+ mFeatureFlags.set(Flags.LEAVE_SHADE_OPEN_FOR_BUGREPORT, true);
+
+ // WHEN collapseShadeForBugreport is called
+ mCentralSurfaces.collapseShadeForBugreport();
+
+ // VERIFY that animateCollapsePanels is called
+ verify(mShadeController, never()).animateCollapsePanels();
+ }
+
+ @Test
public void deviceStateChange_unfolded_shadeOpen_setsLeaveOpenOnKeyguardHide() {
when(mKeyguardStateController.isShowing()).thenReturn(false);
setFoldedStates(FOLD_STATE_FOLDED);
@@ -1102,7 +1159,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
NotificationInterruptLogger logger,
Handler mainHandler,
NotifPipelineFlags flags,
- KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
+ KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
+ UiEventLogger uiEventLogger) {
super(
contentResolver,
powerManager,
@@ -1115,7 +1173,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
logger,
mainHandler,
flags,
- keyguardNotificationVisibilityProvider
+ keyguardNotificationVisibilityProvider,
+ uiEventLogger
);
mUseHeadsUp = true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 8da8d049516e..0c35659b458a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -117,7 +117,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private BouncerCallbackInteractor mBouncerCallbackInteractor;
@Mock private BouncerInteractor mBouncerInteractor;
@Mock private BouncerView mBouncerView;
-// @Mock private WeakReference<BouncerViewDelegate> mBouncerViewDelegateWeakReference;
@Mock private BouncerViewDelegate mBouncerViewDelegate;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
index 1ee8875eada8..78a4db1224cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
@@ -20,7 +20,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogcatEchoTracker
import com.android.systemui.statusbar.DisableFlagsLogger
import com.google.common.truth.Truth.assertThat
import org.junit.Test
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 63467e7039d4..438271c489e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -49,9 +49,9 @@ import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogcatEchoTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeExpansionStateManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
index bf432388ad28..eba3b04f3472 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
@@ -20,7 +20,6 @@ import android.content.Intent
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
@@ -34,8 +33,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -91,7 +90,7 @@ class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() {
fun testStartActivity() {
`when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(false)
statusBarUserSwitcherContainer.callOnClick()
- verify(userSwitcherDialogController).showDialog(any(View::class.java))
+ verify(userSwitcherDialogController).showDialog(any(), any())
`when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(true)
statusBarUserSwitcherContainer.callOnClick()
verify(activityStarter).startActivity(any(Intent::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
new file mode 100644
index 000000000000..b7a6c0125cfa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.airplane.data.repository
+
+import android.os.Handler
+import android.os.Looper
+import android.os.UserHandle
+import android.provider.Settings.Global
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class AirplaneModeRepositoryImplTest : SysuiTestCase() {
+
+ private lateinit var underTest: AirplaneModeRepositoryImpl
+
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ private lateinit var bgHandler: Handler
+ private lateinit var scope: CoroutineScope
+ private lateinit var settings: FakeSettings
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ bgHandler = Handler(Looper.getMainLooper())
+ scope = CoroutineScope(IMMEDIATE)
+ settings = FakeSettings()
+ settings.userId = UserHandle.USER_ALL
+
+ underTest =
+ AirplaneModeRepositoryImpl(
+ bgHandler,
+ settings,
+ logger,
+ scope,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun isAirplaneMode_initiallyGetsSettingsValue() =
+ runBlocking(IMMEDIATE) {
+ settings.putInt(Global.AIRPLANE_MODE_ON, 1)
+
+ underTest =
+ AirplaneModeRepositoryImpl(
+ bgHandler,
+ settings,
+ logger,
+ scope,
+ )
+
+ val job = underTest.isAirplaneMode.launchIn(this)
+
+ assertThat(underTest.isAirplaneMode.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isAirplaneMode_settingUpdated_valueUpdated() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isAirplaneMode.launchIn(this)
+
+ settings.putInt(Global.AIRPLANE_MODE_ON, 0)
+ yield()
+ assertThat(underTest.isAirplaneMode.value).isFalse()
+
+ settings.putInt(Global.AIRPLANE_MODE_ON, 1)
+ yield()
+ assertThat(underTest.isAirplaneMode.value).isTrue()
+
+ settings.putInt(Global.AIRPLANE_MODE_ON, 0)
+ yield()
+ assertThat(underTest.isAirplaneMode.value).isFalse()
+
+ job.cancel()
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
new file mode 100644
index 000000000000..63bbdfca0071
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.airplane.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeAirplaneModeRepository : AirplaneModeRepository {
+ private val _isAirplaneMode = MutableStateFlow(false)
+ override val isAirplaneMode: StateFlow<Boolean> = _isAirplaneMode
+
+ fun setIsAirplaneMode(isAirplaneMode: Boolean) {
+ _isAirplaneMode.value = isAirplaneMode
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
new file mode 100644
index 000000000000..33a80e1a3dd6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.airplane.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+class AirplaneModeInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: AirplaneModeInteractor
+
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+ private lateinit var connectivityRepository: FakeConnectivityRepository
+
+ @Before
+ fun setUp() {
+ airplaneModeRepository = FakeAirplaneModeRepository()
+ connectivityRepository = FakeConnectivityRepository()
+ underTest = AirplaneModeInteractor(airplaneModeRepository, connectivityRepository)
+ }
+
+ @Test
+ fun isAirplaneMode_matchesRepo() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isAirplaneMode.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ yield()
+ assertThat(latest).isTrue()
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ yield()
+ assertThat(latest).isFalse()
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ yield()
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isForceHidden_repoHasWifiHidden_outputsTrue() =
+ runBlocking(IMMEDIATE) {
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
+
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ connectivityRepository.setForceHiddenIcons(setOf())
+
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
new file mode 100644
index 000000000000..76016a121e68
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+class AirplaneModeViewModelTest : SysuiTestCase() {
+
+ private lateinit var underTest: AirplaneModeViewModel
+
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+ private lateinit var connectivityRepository: FakeConnectivityRepository
+ private lateinit var interactor: AirplaneModeInteractor
+ private lateinit var scope: CoroutineScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ airplaneModeRepository = FakeAirplaneModeRepository()
+ connectivityRepository = FakeConnectivityRepository()
+ interactor = AirplaneModeInteractor(airplaneModeRepository, connectivityRepository)
+ scope = CoroutineScope(IMMEDIATE)
+
+ underTest =
+ AirplaneModeViewModel(
+ interactor,
+ logger,
+ scope,
+ )
+ }
+
+ @Test
+ fun isAirplaneModeIconVisible_notAirplaneMode_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ connectivityRepository.setForceHiddenIcons(setOf())
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ var latest: Boolean? = null
+ val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isAirplaneModeIconVisible_forceHidden_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ var latest: Boolean? = null
+ val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isAirplaneModeIconVisible_isAirplaneModeAndNotForceHidden_outputsTrue() =
+ runBlocking(IMMEDIATE) {
+ connectivityRepository.setForceHiddenIcons(setOf())
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ var latest: Boolean? = null
+ val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
index 0e75c74ef6f5..b32058fca109 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
@@ -22,7 +22,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogcatEchoTracker
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index f751afc195b2..2f18ce31217e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -27,6 +27,9 @@ class FakeWifiRepository : WifiRepository {
private val _isWifiEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled
+ private val _isWifiDefault: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val isWifiDefault: StateFlow<Boolean> = _isWifiDefault
+
private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> =
MutableStateFlow(WifiNetworkModel.Inactive)
override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
@@ -38,6 +41,10 @@ class FakeWifiRepository : WifiRepository {
_isWifiEnabled.value = enabled
}
+ fun setIsWifiDefault(default: Boolean) {
+ _isWifiDefault.value = default
+ }
+
fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) {
_wifiNetwork.value = wifiNetworkModel
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index 0ba0bd623c39..a64a4bd2e57a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -222,6 +222,83 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun isWifiDefault_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ assertThat(underTest.isWifiDefault.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiDefault_wifiNetwork_isTrue() = runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val wifiInfo = mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(SSID)
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(
+ NETWORK,
+ createWifiNetworkCapabilities(wifiInfo)
+ )
+
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiDefault_cellularVcnNetwork_isTrue() = runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val capabilities = mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiDefault_cellularNotVcnNetwork_isFalse() = runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val capabilities = mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(mock())
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+ assertThat(underTest.isWifiDefault.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiDefault_wifiNetworkLost_isFalse() = runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ // First, add a network
+ getDefaultNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ // WHEN the network is lost
+ getDefaultNetworkCallback().onLost(NETWORK)
+
+ // THEN we update to false
+ assertThat(underTest.isWifiDefault.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
val job = underTest
@@ -745,6 +822,12 @@ class WifiRepositoryImplTest : SysuiTestCase() {
return callbackCaptor.value!!
}
+ private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+ val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+ verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
private fun createWifiNetworkCapabilities(
wifiInfo: WifiInfo,
isValidated: Boolean = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
index 39b886af1cb8..71b8bab87d19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -178,6 +178,29 @@ class WifiInteractorTest : SysuiTestCase() {
}
@Test
+ fun isDefault_matchesRepoIsDefault() = runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest
+ .isDefault
+ .onEach { latest = it }
+ .launchIn(this)
+
+ wifiRepository.setIsWifiDefault(true)
+ yield()
+ assertThat(latest).isTrue()
+
+ wifiRepository.setIsWifiDefault(false)
+ yield()
+ assertThat(latest).isFalse()
+
+ wifiRepository.setIsWifiDefault(true)
+ yield()
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
fun wifiNetwork_matchesRepoWifiNetwork() = runBlocking(IMMEDIATE) {
val wifiNetwork = WifiNetworkModel.Active(
networkId = 45,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 4efb13520ebf..c5841098010a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -30,6 +30,9 @@ import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
@@ -63,11 +66,13 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
private lateinit var connectivityConstants: ConnectivityConstants
@Mock
private lateinit var wifiConstants: WifiConstants
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
private lateinit var viewModel: WifiViewModel
private lateinit var scope: CoroutineScope
+ private lateinit var airplaneModeViewModel: AirplaneModeViewModel
@JvmField @Rule
val instantTaskExecutor = InstantTaskExecutorRule()
@@ -77,12 +82,22 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
+ airplaneModeRepository = FakeAirplaneModeRepository()
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
scope = CoroutineScope(Dispatchers.Unconfined)
+ airplaneModeViewModel = AirplaneModeViewModel(
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ connectivityRepository,
+ ),
+ logger,
+ scope,
+ )
viewModel = WifiViewModel(
+ airplaneModeViewModel,
connectivityConstants,
context,
logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index a3ad028519bb..a1afcd71e3c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -22,11 +22,14 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
@@ -64,19 +67,31 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
+ private lateinit var airplaneModeViewModel: AirplaneModeViewModel
private lateinit var scope: CoroutineScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ airplaneModeRepository = FakeAirplaneModeRepository()
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
+ airplaneModeViewModel =
+ AirplaneModeViewModel(
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ connectivityRepository,
+ ),
+ logger,
+ scope,
+ )
}
@After
@@ -88,6 +103,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
fun wifiIcon() =
runBlocking(IMMEDIATE) {
wifiRepository.setIsWifiEnabled(testCase.enabled)
+ wifiRepository.setIsWifiDefault(testCase.isDefault)
connectivityRepository.setForceHiddenIcons(
if (testCase.forceHidden) {
setOf(ConnectivitySlot.WIFI)
@@ -101,6 +117,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
.thenReturn(testCase.hasDataCapabilities)
underTest =
WifiViewModel(
+ airplaneModeViewModel,
connectivityConstants,
context,
logger,
@@ -125,19 +142,12 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
} else {
testCase.expected.contentDescription.invoke(context)
}
- assertThat(iconFlow.value?.contentDescription?.getAsString())
+ assertThat(iconFlow.value?.contentDescription?.loadContentDescription(context))
.isEqualTo(expectedContentDescription)
job.cancel()
}
- private fun ContentDescription.getAsString(): String? {
- return when (this) {
- is ContentDescription.Loaded -> this.description
- is ContentDescription.Resource -> context.getString(this.res)
- }
- }
-
internal data class Expected(
/** The resource that should be used for the icon. */
@DrawableRes val iconResource: Int,
@@ -159,6 +169,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
val forceHidden: Boolean = false,
val alwaysShowIconWhenEnabled: Boolean = false,
val hasDataCapabilities: Boolean = true,
+ val isDefault: Boolean = false,
val network: WifiNetworkModel,
/** The expected output. Null if we expect the output to be null. */
@@ -169,6 +180,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
"forceHidden=$forceHidden, " +
"showWhenEnabled=$alwaysShowIconWhenEnabled, " +
"hasDataCaps=$hasDataCapabilities, " +
+ "isDefault=$isDefault, " +
"network=$network) then " +
"EXPECTED($expected)"
}
@@ -303,6 +315,46 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
),
),
+ // isDefault = true => all Inactive and Active networks shown
+ TestCase(
+ isDefault = true,
+ network = WifiNetworkModel.Inactive,
+ expected =
+ Expected(
+ iconResource = WIFI_NO_NETWORK,
+ contentDescription = { context ->
+ "${context.getString(WIFI_NO_CONNECTION)}," +
+ context.getString(NO_INTERNET)
+ },
+ description = "No network icon",
+ ),
+ ),
+ TestCase(
+ isDefault = true,
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3),
+ expected =
+ Expected(
+ iconResource = WIFI_NO_INTERNET_ICONS[3],
+ contentDescription = { context ->
+ "${context.getString(WIFI_CONNECTION_STRENGTH[3])}," +
+ context.getString(NO_INTERNET)
+ },
+ description = "No internet level 3 icon",
+ ),
+ ),
+ TestCase(
+ isDefault = true,
+ network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1),
+ expected =
+ Expected(
+ iconResource = WIFI_FULL_ICONS[1],
+ contentDescription = { context ->
+ context.getString(WIFI_CONNECTION_STRENGTH[1])
+ },
+ description = "Full internet level 1 icon",
+ ),
+ ),
+
// network = CarrierMerged => not shown
TestCase(
network = WifiNetworkModel.CarrierMerged,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 3169eef83f07..7d2c56098584 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -20,8 +20,12 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
@@ -55,19 +59,31 @@ class WifiViewModelTest : SysuiTestCase() {
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
+ private lateinit var airplaneModeViewModel: AirplaneModeViewModel
private lateinit var scope: CoroutineScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ airplaneModeRepository = FakeAirplaneModeRepository()
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
+ airplaneModeViewModel = AirplaneModeViewModel(
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ connectivityRepository,
+ ),
+ logger,
+ scope,
+ )
+
createAndSetViewModel()
}
@@ -76,6 +92,8 @@ class WifiViewModelTest : SysuiTestCase() {
scope.cancel()
}
+ // See [WifiViewModelIconParameterizedTest] for additional view model tests.
+
// Note on testing: [WifiViewModel] exposes 3 different instances of
// [LocationBasedWifiViewModel]. In practice, these 3 different instances will get the exact
// same data for icon, activity, etc. flows. So, most of these tests will test just one of the
@@ -460,11 +478,64 @@ class WifiViewModelTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun airplaneSpacer_notAirplaneMode_outputsFalse() = runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest
+ .qs
+ .isAirplaneSpacerVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun airplaneSpacer_airplaneForceHidden_outputsFalse() = runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest
+ .qs
+ .isAirplaneSpacerVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun airplaneSpacer_airplaneIconVisible_outputsTrue() = runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest
+ .qs
+ .isAirplaneSpacerVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
private fun createAndSetViewModel() {
// [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow
// creations rely on certain config values that we mock out in individual tests. This method
// allows tests to create the view model only after those configs are correctly set up.
underTest = WifiViewModel(
+ airplaneModeViewModel,
connectivityConstants,
context,
logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 6ace4044b3f7..915e999c2646 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -23,8 +23,12 @@ import static junit.framework.Assert.assertNotNull;
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.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.PendingIntent;
@@ -43,10 +47,14 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.ContentInfo;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.EditText;
import android.widget.ImageButton;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
+import android.window.WindowOnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
@@ -67,6 +75,7 @@ import org.junit.After;
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;
@@ -229,6 +238,67 @@ public class RemoteInputViewTest extends SysuiTestCase {
}
@Test
+ public void testPredictiveBack_registerAndUnregister() throws Exception {
+ NotificationTestHelper helper = new NotificationTestHelper(
+ mContext,
+ mDependency,
+ TestableLooper.get(this));
+ ExpandableNotificationRow row = helper.createRow();
+ RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+
+ ViewRootImpl viewRoot = mock(ViewRootImpl.class);
+ WindowOnBackInvokedDispatcher backInvokedDispatcher = mock(
+ WindowOnBackInvokedDispatcher.class);
+ ArgumentCaptor<OnBackInvokedCallback> onBackInvokedCallbackCaptor = ArgumentCaptor.forClass(
+ OnBackInvokedCallback.class);
+ when(viewRoot.getOnBackInvokedDispatcher()).thenReturn(backInvokedDispatcher);
+ view.setViewRootImpl(viewRoot);
+
+ /* verify that predictive back callback registered when RemoteInputView becomes visible */
+ view.onVisibilityAggregated(true);
+ verify(backInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ onBackInvokedCallbackCaptor.capture());
+
+ /* verify that same callback unregistered when RemoteInputView becomes invisible */
+ view.onVisibilityAggregated(false);
+ verify(backInvokedDispatcher).unregisterOnBackInvokedCallback(
+ eq(onBackInvokedCallbackCaptor.getValue()));
+ }
+
+ @Test
+ public void testUiPredictiveBack_openAndDispatchCallback() throws Exception {
+ NotificationTestHelper helper = new NotificationTestHelper(
+ mContext,
+ mDependency,
+ TestableLooper.get(this));
+ ExpandableNotificationRow row = helper.createRow();
+ RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+ ViewRootImpl viewRoot = mock(ViewRootImpl.class);
+ WindowOnBackInvokedDispatcher backInvokedDispatcher = mock(
+ WindowOnBackInvokedDispatcher.class);
+ ArgumentCaptor<OnBackInvokedCallback> onBackInvokedCallbackCaptor = ArgumentCaptor.forClass(
+ OnBackInvokedCallback.class);
+ when(viewRoot.getOnBackInvokedDispatcher()).thenReturn(backInvokedDispatcher);
+ view.setViewRootImpl(viewRoot);
+ view.onVisibilityAggregated(true);
+ view.setEditTextReferenceToSelf();
+
+ /* capture the callback during registration */
+ verify(backInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+ onBackInvokedCallbackCaptor.capture());
+
+ view.focus();
+
+ /* invoke the captured callback */
+ onBackInvokedCallbackCaptor.getValue().onBackInvoked();
+
+ /* verify that the RemoteInputView goes away */
+ assertEquals(view.getVisibility(), View.GONE);
+ }
+
+ @Test
public void testUiEventLogging_openAndSend() throws Exception {
NotificationTestHelper helper = new NotificationTestHelper(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index c4abedd0eed4..b68eb88d46db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -63,8 +63,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
@Mock
private lateinit var powerManager: PowerManager
- private var shouldIgnoreViewRemoval: Boolean = false
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -209,26 +207,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
verify(windowManager, never()).removeView(any())
}
- @Test
- fun removeView_shouldIgnoreRemovalFalse_viewRemoved() {
- shouldIgnoreViewRemoval = false
- underTest.displayView(getState())
-
- underTest.removeView("reason")
-
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun removeView_shouldIgnoreRemovalTrue_viewNotRemoved() {
- shouldIgnoreViewRemoval = true
- underTest.displayView(getState())
-
- underTest.removeView("reason")
-
- verify(windowManager, never()).removeView(any())
- }
-
private fun getState(name: String = "name") = ViewInfo(name)
private fun getConfigurationListener(): ConfigurationListener {
@@ -253,7 +231,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
accessibilityManager,
configurationController,
powerManager,
- R.layout.media_ttt_chip,
+ R.layout.chipbar,
"Window Title",
"WAKE_REASON",
) {
@@ -267,10 +245,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
mostRecentViewInfo = newInfo
}
- override fun shouldIgnoreViewRemoval(info: ViewInfo, removalReason: String): Boolean {
- return shouldIgnoreViewRemoval
- }
-
override fun getTouchableRegion(view: View, outRect: Rect) {
outRect.setEmpty()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index c9f2b4db81ef..13e9f608158e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -19,9 +19,9 @@ package com.android.systemui.temporarydisplay
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
new file mode 100644
index 000000000000..9fbf159ec348
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -0,0 +1,352 @@
+/*
+ * 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.temporarydisplay.chipbar
+
+import android.os.PowerManager
+import android.os.VibrationEffect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.view.ViewUtil
+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.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ChipbarCoordinatorTest : SysuiTestCase() {
+ private lateinit var underTest: FakeChipbarCoordinator
+
+ @Mock private lateinit var logger: MediaTttLogger
+ @Mock private lateinit var accessibilityManager: AccessibilityManager
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var powerManager: PowerManager
+ @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var viewUtil: ViewUtil
+ @Mock private lateinit var vibratorHelper: VibratorHelper
+ private lateinit var fakeClock: FakeSystemClock
+ private lateinit var fakeExecutor: FakeExecutor
+ private lateinit var uiEventLoggerFake: UiEventLoggerFake
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
+
+ fakeClock = FakeSystemClock()
+ fakeExecutor = FakeExecutor(fakeClock)
+
+ uiEventLoggerFake = UiEventLoggerFake()
+
+ underTest =
+ FakeChipbarCoordinator(
+ context,
+ logger,
+ windowManager,
+ fakeExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ falsingManager,
+ falsingCollector,
+ viewUtil,
+ vibratorHelper,
+ )
+ underTest.start()
+ }
+
+ @Test
+ fun displayView_loadedIcon_correctlyRendered() {
+ val drawable = context.getDrawable(R.drawable.ic_celebration)!!
+
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
+ Text.Loaded("text"),
+ endItem = null,
+ )
+ )
+
+ val iconView = getChipbarView().getStartIconView()
+ assertThat(iconView.drawable).isEqualTo(drawable)
+ assertThat(iconView.contentDescription).isEqualTo("loadedCD")
+ }
+
+ @Test
+ fun displayView_resourceIcon_correctlyRendered() {
+ val contentDescription = ContentDescription.Resource(R.string.controls_error_timeout)
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, contentDescription),
+ Text.Loaded("text"),
+ endItem = null,
+ )
+ )
+
+ val iconView = getChipbarView().getStartIconView()
+ assertThat(iconView.contentDescription)
+ .isEqualTo(contentDescription.loadContentDescription(context))
+ }
+
+ @Test
+ fun displayView_loadedText_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("display view text here"),
+ endItem = null,
+ )
+ )
+
+ assertThat(getChipbarView().getChipText()).isEqualTo("display view text here")
+ }
+
+ @Test
+ fun displayView_resourceText_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Resource(R.string.screenrecord_start_error),
+ endItem = null,
+ )
+ )
+
+ assertThat(getChipbarView().getChipText())
+ .isEqualTo(context.getString(R.string.screenrecord_start_error))
+ }
+
+ @Test
+ fun displayView_endItemNull_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = null,
+ )
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun displayView_endItemLoading_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun displayView_endItemError_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Error,
+ )
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun displayView_endItemButton_correctlyRendered() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem =
+ ChipbarEndItem.Button(
+ Text.Loaded("button text"),
+ onClickListener = {},
+ ),
+ )
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getEndButton().text).isEqualTo("button text")
+ assertThat(chipbarView.getEndButton().hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun displayView_endItemButtonClicked_falseTap_listenerNotRun() {
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
+ var isClicked = false
+ val buttonClickListener = View.OnClickListener { isClicked = true }
+
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem =
+ ChipbarEndItem.Button(
+ Text.Loaded("button text"),
+ buttonClickListener,
+ ),
+ )
+ )
+
+ getChipbarView().getEndButton().performClick()
+
+ assertThat(isClicked).isFalse()
+ }
+
+ @Test
+ fun displayView_endItemButtonClicked_notFalseTap_listenerRun() {
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false)
+ var isClicked = false
+ val buttonClickListener = View.OnClickListener { isClicked = true }
+
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem =
+ ChipbarEndItem.Button(
+ Text.Loaded("button text"),
+ buttonClickListener,
+ ),
+ )
+ )
+
+ getChipbarView().getEndButton().performClick()
+
+ assertThat(isClicked).isTrue()
+ }
+
+ @Test
+ fun displayView_vibrationEffect_doubleClickEffect() {
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = null,
+ vibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
+ )
+ )
+
+ verify(vibratorHelper).vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK))
+ }
+
+ @Test
+ fun updateView_viewUpdated() {
+ // First, display a view
+ val drawable = context.getDrawable(R.drawable.ic_celebration)!!
+
+ underTest.displayView(
+ ChipbarInfo(
+ Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ val chipbarView = getChipbarView()
+ assertThat(chipbarView.getStartIconView().drawable).isEqualTo(drawable)
+ assertThat(chipbarView.getStartIconView().contentDescription).isEqualTo("loadedCD")
+ assertThat(chipbarView.getChipText()).isEqualTo("title text")
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
+
+ // WHEN the view is updated
+ val newDrawable = context.getDrawable(R.drawable.ic_cake)!!
+ underTest.updateView(
+ ChipbarInfo(
+ Icon.Loaded(newDrawable, ContentDescription.Loaded("new CD")),
+ Text.Loaded("new title text"),
+ endItem = ChipbarEndItem.Error,
+ ),
+ chipbarView
+ )
+
+ // THEN we display the new view
+ assertThat(chipbarView.getStartIconView().drawable).isEqualTo(newDrawable)
+ assertThat(chipbarView.getStartIconView().contentDescription).isEqualTo("new CD")
+ assertThat(chipbarView.getChipText()).isEqualTo("new title text")
+ assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+ assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
+ assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
+ }
+
+ private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
+
+ private fun ViewGroup.getChipText(): String =
+ (this.requireViewById<TextView>(R.id.text)).text as String
+
+ private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading)
+
+ private fun ViewGroup.getEndButton(): TextView = this.requireViewById(R.id.end_button)
+
+ private fun ViewGroup.getErrorIcon(): View = this.requireViewById(R.id.error)
+
+ private fun getChipbarView(): ViewGroup {
+ val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+ verify(windowManager).addView(viewCaptor.capture(), any())
+ return viewCaptor.value as ViewGroup
+ }
+}
+
+private const val TIMEOUT = 10000
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
new file mode 100644
index 000000000000..17d402319246
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.temporarydisplay.chipbar
+
+import android.content.Context
+import android.os.PowerManager
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
+
+/** A fake implementation of [ChipbarCoordinator] for testing. */
+class FakeChipbarCoordinator(
+ context: Context,
+ @MediaTttReceiverLogger logger: MediaTttLogger,
+ windowManager: WindowManager,
+ mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
+ falsingManager: FalsingManager,
+ falsingCollector: FalsingCollector,
+ viewUtil: ViewUtil,
+ vibratorHelper: VibratorHelper,
+) :
+ ChipbarCoordinator(
+ context,
+ logger,
+ windowManager,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ falsingManager,
+ falsingCollector,
+ viewUtil,
+ vibratorHelper,
+ ) {
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ // Just bypass the animation in tests
+ onAnimationEnd.run()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
index d951f366c595..525d8371c9ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
@@ -110,7 +110,7 @@ class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
val thirdExpectedValue =
setUpUsers(
count = 2,
- hasGuest = true,
+ isLastGuestUser = true,
selectedIndex = 1,
)
underTest.refreshUsers()
@@ -121,21 +121,25 @@ class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
}
@Test
- fun `refreshUsers - sorts by creation time`() = runSelfCancelingTest {
+ fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
underTest = create(this)
val unsortedUsers =
setUpUsers(
count = 3,
selectedIndex = 0,
+ isLastGuestUser = true,
+ )
+ unsortedUsers[0].creationTime = 999
+ unsortedUsers[1].creationTime = 900
+ unsortedUsers[2].creationTime = 950
+ val expectedUsers =
+ listOf(
+ unsortedUsers[1],
+ unsortedUsers[0],
+ unsortedUsers[2], // last because this is the guest
)
- unsortedUsers[0].creationTime = 900
- unsortedUsers[1].creationTime = 700
- unsortedUsers[2].creationTime = 999
- val expectedUsers = listOf(unsortedUsers[1], unsortedUsers[0], unsortedUsers[2])
var userInfos: List<UserInfo>? = null
- var selectedUserInfo: UserInfo? = null
underTest.userInfos.onEach { userInfos = it }.launchIn(this)
- underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
underTest.refreshUsers()
assertThat(userInfos).isEqualTo(expectedUsers)
@@ -143,14 +147,14 @@ class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
private fun setUpUsers(
count: Int,
- hasGuest: Boolean = false,
+ isLastGuestUser: Boolean = false,
selectedIndex: Int = 0,
): List<UserInfo> {
val userInfos =
(0 until count).map { index ->
createUserInfo(
index,
- isGuest = hasGuest && index == count - 1,
+ isGuest = isLastGuestUser && index == count - 1,
)
}
whenever(manager.aliveUsers).thenReturn(userInfos)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
index d4b41c18e123..a363a037c499 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
@@ -97,6 +97,7 @@ class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
createUserRecord(2),
createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
createActionRecord(UserActionModel.ENTER_GUEST_MODE),
+ createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
)
)
var models: List<UserModel>? = null
@@ -176,15 +177,17 @@ class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
createUserRecord(2),
createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
createActionRecord(UserActionModel.ENTER_GUEST_MODE),
+ createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
)
)
var models: List<UserActionModel>? = null
val job = underTest.actions.onEach { models = it }.launchIn(this)
- assertThat(models).hasSize(3)
+ assertThat(models).hasSize(4)
assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
+ assertThat(models?.get(3)).isEqualTo(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
job.cancel()
}
@@ -200,6 +203,7 @@ class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
isAddUser = action == UserActionModel.ADD_USER,
isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
isGuest = action == UserActionModel.ENTER_GUEST_MODE,
+ isManageUsers = action == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
index 1540f8552002..97571b23be56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
@@ -28,6 +28,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.R.drawable.ic_account_circle
import com.android.systemui.R
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.domain.model.ShowDialogRequestModel
@@ -316,14 +317,16 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
keyguardRepository.setKeyguardShowing(false)
var dialogRequest: ShowDialogRequestModel? = null
val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogShower: UserSwitchDialogController.DialogShower = mock()
- underTest.executeAction(UserActionModel.ADD_USER)
+ underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
assertThat(dialogRequest)
.isEqualTo(
ShowDialogRequestModel.ShowAddUserDialog(
userHandle = userInfos[0].userHandle,
isKeyguardShowing = false,
showEphemeralMessage = false,
+ dialogShower = dialogShower,
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
index c3a9705bf6ba..6a17c8ddc63d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
@@ -64,13 +64,7 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() {
@Test
fun `actions - not actionable when locked and not locked`() =
runBlocking(IMMEDIATE) {
- userRepository.setActions(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- )
- )
+ setActions()
userRepository.setActionableWhenLocked(false)
keyguardRepository.setKeyguardShowing(false)
@@ -92,13 +86,7 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() {
@Test
fun `actions - actionable when locked and not locked`() =
runBlocking(IMMEDIATE) {
- userRepository.setActions(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- )
- )
+ setActions()
userRepository.setActionableWhenLocked(true)
keyguardRepository.setKeyguardShowing(false)
@@ -120,13 +108,7 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() {
@Test
fun `actions - actionable when locked and locked`() =
runBlocking(IMMEDIATE) {
- userRepository.setActions(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- )
- )
+ setActions()
userRepository.setActionableWhenLocked(true)
keyguardRepository.setKeyguardShowing(true)
@@ -182,6 +164,10 @@ open class UserInteractorUnrefactoredTest : UserInteractorTest() {
verify(activityStarter).startActivity(any(), anyBoolean())
}
+ private fun setActions() {
+ userRepository.setActions(UserActionModel.values().toList())
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 0344e3f991e2..c12a868dbaed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -268,6 +268,26 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
}
@Test
+ fun `menu actions`() =
+ runBlocking(IMMEDIATE) {
+ userRepository.setActions(UserActionModel.values().toList())
+ var actions: List<UserActionViewModel>? = null
+ val job = underTest.menu.onEach { actions = it }.launchIn(this)
+
+ assertThat(actions?.map { it.viewKey })
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
+ UserActionModel.ADD_USER.ordinal.toLong(),
+ UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
fun `isFinishRequested - finishes when user is switched`() =
runBlocking(IMMEDIATE) {
setUsers(count = 2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt
deleted file mode 100644
index 5e09b81da4e8..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/util/collection/RingBufferTest.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.collection
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertSame
-import org.junit.Assert.assertThrows
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class RingBufferTest : SysuiTestCase() {
-
- private val buffer = RingBuffer(5) { TestElement() }
-
- private val history = mutableListOf<TestElement>()
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- }
-
- @Test
- fun testBarelyFillBuffer() {
- fillBuffer(5)
-
- assertEquals(0, buffer[0].id)
- assertEquals(1, buffer[1].id)
- assertEquals(2, buffer[2].id)
- assertEquals(3, buffer[3].id)
- assertEquals(4, buffer[4].id)
- }
-
- @Test
- fun testPartiallyFillBuffer() {
- fillBuffer(3)
-
- assertEquals(3, buffer.size)
-
- assertEquals(0, buffer[0].id)
- assertEquals(1, buffer[1].id)
- assertEquals(2, buffer[2].id)
-
- assertThrows(IndexOutOfBoundsException::class.java) { buffer[3] }
- assertThrows(IndexOutOfBoundsException::class.java) { buffer[4] }
- }
-
- @Test
- fun testSpinBuffer() {
- fillBuffer(277)
-
- assertEquals(272, buffer[0].id)
- assertEquals(273, buffer[1].id)
- assertEquals(274, buffer[2].id)
- assertEquals(275, buffer[3].id)
- assertEquals(276, buffer[4].id)
- assertThrows(IndexOutOfBoundsException::class.java) { buffer[5] }
-
- assertEquals(5, buffer.size)
- }
-
- @Test
- fun testElementsAreRecycled() {
- fillBuffer(23)
-
- assertSame(history[4], buffer[1])
- assertSame(history[9], buffer[1])
- assertSame(history[14], buffer[1])
- assertSame(history[19], buffer[1])
- }
-
- @Test
- fun testIterator() {
- fillBuffer(13)
-
- val iterator = buffer.iterator()
-
- for (i in 0 until 5) {
- assertEquals(history[8 + i], iterator.next())
- }
- assertFalse(iterator.hasNext())
- assertThrows(NoSuchElementException::class.java) { iterator.next() }
- }
-
- @Test
- fun testForEach() {
- fillBuffer(13)
- var i = 8
-
- buffer.forEach {
- assertEquals(history[i], it)
- i++
- }
- assertEquals(13, i)
- }
-
- private fun fillBuffer(count: Int) {
- for (i in 0 until count) {
- val elem = buffer.advance()
- elem.id = history.size
- history.add(elem)
- }
- }
-}
-
-private class TestElement(var id: Int = 0) {
- override fun toString(): String {
- return "{TestElement $id}"
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 09da52e7685c..fa7ebf6a2449 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -80,6 +80,7 @@ import android.view.WindowManager;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
@@ -91,6 +92,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
@@ -190,6 +192,8 @@ public class BubblesTest extends SysuiTestCase {
private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock
private AuthController mAuthController;
+ @Mock
+ private ShadeExpansionStateManager mShadeExpansionStateManager;
private SysUiState mSysUiState;
private boolean mSysUiStateBubblesExpanded;
@@ -290,7 +294,7 @@ public class BubblesTest extends SysuiTestCase {
mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
mColorExtractor, mDumpManager, mKeyguardStateController,
- mScreenOffAnimationController, mAuthController);
+ mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager);
mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
mNotificationShadeWindowController.attach();
@@ -343,7 +347,8 @@ public class BubblesTest extends SysuiTestCase {
mock(NotificationInterruptLogger.class),
mock(Handler.class),
mock(NotifPipelineFlags.class),
- mock(KeyguardNotificationVisibilityProvider.class)
+ mock(KeyguardNotificationVisibilityProvider.class),
+ mock(UiEventLogger.class)
);
when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
mBubbleController = new TestableBubbleController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index 9635faf6e858..e5316bc83a12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -22,6 +22,7 @@ import android.os.Handler;
import android.os.PowerManager;
import android.service.dreams.IDreamManager;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
@@ -46,7 +47,8 @@ public class TestableNotificationInterruptStateProviderImpl
NotificationInterruptLogger logger,
Handler mainHandler,
NotifPipelineFlags flags,
- KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
+ KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
+ UiEventLogger uiEventLogger) {
super(contentResolver,
powerManager,
dreamManager,
@@ -58,7 +60,8 @@ public class TestableNotificationInterruptStateProviderImpl
logger,
mainHandler,
flags,
- keyguardNotificationVisibilityProvider);
+ keyguardNotificationVisibilityProvider,
+ uiEventLogger);
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index cebe946a459c..7af66f641837 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -34,6 +34,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.desktopmode.DesktopMode;
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -49,6 +51,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
+import java.util.concurrent.Executor;
/**
* Tests for {@link WMShell}.
@@ -76,12 +79,14 @@ public class WMShellTest extends SysuiTestCase {
@Mock UserTracker mUserTracker;
@Mock ShellExecutor mSysUiMainExecutor;
@Mock FloatingTasks mFloatingTasks;
+ @Mock DesktopMode mDesktopMode;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mFloatingTasks),
+ Optional.of(mDesktopMode),
mCommandQueue, mConfigurationController, mKeyguardStateController,
mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer,
mWakefulnessLifecycle, mUserTracker, mSysUiMainExecutor);
@@ -103,4 +108,12 @@ public class WMShellTest extends SysuiTestCase {
verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class));
}
+
+ @Test
+ public void initDesktopMode_registersListener() {
+ mWMShell.initDesktopMode(mDesktopMode);
+ verify(mDesktopMode).addListener(
+ any(DesktopModeTaskRepository.VisibleTasksListener.class),
+ any(Executor.class));
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index 5d52be2675e3..a60b7735fbd4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -26,7 +26,7 @@ class FakeFeatureFlags : FeatureFlags {
private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>()
init {
- Flags.getFlagFields().forEach { field ->
+ Flags.flagFields.forEach { field ->
val flag: Flag<*> = field.get(null) as Flag<*>
knownFlagNames[flag.id] = field.name
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 725b1f41372c..0c126805fb78 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import com.android.systemui.common.shared.model.Position
+import com.android.systemui.keyguard.shared.model.StatusBarState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -44,6 +45,9 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _dozeAmount = MutableStateFlow(0f)
override val dozeAmount: Flow<Float> = _dozeAmount
+ private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
+ override val statusBarState: Flow<StatusBarState> = _statusBarState
+
override fun isKeyguardShowing(): Boolean {
return _isKeyguardShowing.value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
index 527258579372..c33ce5d9484d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
@@ -16,7 +16,7 @@
package com.android.systemui.qs
-import android.view.View
+import com.android.systemui.animation.Expandable
import com.android.systemui.qs.FgsManagerController.OnDialogDismissedListener
import com.android.systemui.qs.FgsManagerController.OnNumberOfPackagesChangedListener
import kotlinx.coroutines.flow.MutableStateFlow
@@ -54,7 +54,7 @@ class FakeFgsManagerController(
override fun init() {}
- override fun showDialog(viewLaunchedFrom: View?) {}
+ override fun showDialog(expandable: Expandable?) {}
override fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) {
numRunningPackagesListeners.add(listener)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 2a9aeddc9aa8..325da4ead666 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -57,7 +57,6 @@ import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.time.FakeSystemClock
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.test.TestCoroutineDispatcher
@@ -68,7 +67,6 @@ import kotlinx.coroutines.test.TestCoroutineDispatcher
class FooterActionsTestUtils(
private val context: Context,
private val testableLooper: TestableLooper,
- private val fakeClock: FakeSystemClock = FakeSystemClock(),
) {
/** Enable or disable the user switcher in the settings. */
fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6417db0eb050..29194c58bd0c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -657,25 +657,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
userState.mBindingServices.removeIf(filter);
userState.mCrashedServices.removeIf(filter);
final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
+ boolean anyServiceRemoved = false;
while (it.hasNext()) {
final ComponentName comp = it.next();
final String compPkg = comp.getPackageName();
if (compPkg.equals(packageName)) {
it.remove();
- // Update the enabled services setting.
- persistComponentNamesToSettingLocked(
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- userState.mEnabledServices, userId);
- // Update the touch exploration granted services setting.
userState.mTouchExplorationGrantedServices.remove(comp);
- persistComponentNamesToSettingLocked(
- Settings.Secure.
- TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
- userState.mTouchExplorationGrantedServices, userId);
- onUserStateChangedLocked(userState);
- return;
+ anyServiceRemoved = true;
}
}
+ if (anyServiceRemoved) {
+ // Update the enabled services setting.
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mEnabledServices, userId);
+ // Update the touch exploration granted services setting.
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ userState.mTouchExplorationGrantedServices, userId);
+ onUserStateChangedLocked(userState);
+ }
}
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 38237fa8eabd..23cacf3c54cf 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -866,6 +866,33 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
@Override
+ public void setAppWidgetHidden(String callingPackage, int hostId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "setAppWidgetHidden() " + userId);
+ }
+
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId, /* enforceUserUnlockingOrUnlocked */false);
+
+ HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+ Host host = lookupHostLocked(id);
+
+ if (host != null) {
+ try {
+ mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUids(), false);
+ } catch (NullPointerException e) {
+ Slog.e(TAG, "setAppWidgetHidden(): Getting host uids: " + host.toString(), e);
+ throw e;
+ }
+ }
+ }
+ }
+
+ @Override
public void deleteAppWidgetId(String callingPackage, int appWidgetId) {
final int userId = UserHandle.getCallingUserId();
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 8d878af4b788..fcc1ef2a3882 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -152,6 +152,8 @@ final class UiModeManagerService extends SystemService {
// flag set by resource, whether to start dream immediately upon docking even if unlocked.
private boolean mStartDreamImmediatelyOnDock = true;
+ // flag set by resource, whether to disable dreams when ambient mode suppression is enabled.
+ private boolean mDreamsDisabledByAmbientModeSuppression = false;
// flag set by resource, whether to enable Car dock launch when starting car mode.
private boolean mEnableCarDockLaunch = true;
// flag set by resource, whether to lock UI mode to the default one or not.
@@ -364,6 +366,11 @@ final class UiModeManagerService extends SystemService {
mStartDreamImmediatelyOnDock = startDreamImmediatelyOnDock;
}
+ @VisibleForTesting
+ void setDreamsDisabledByAmbientModeSuppression(boolean disabledByAmbientModeSuppression) {
+ mDreamsDisabledByAmbientModeSuppression = disabledByAmbientModeSuppression;
+ }
+
@Override
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
mCurrentUser = to.getUserIdentifier();
@@ -424,6 +431,8 @@ final class UiModeManagerService extends SystemService {
final Resources res = context.getResources();
mStartDreamImmediatelyOnDock = res.getBoolean(
com.android.internal.R.bool.config_startDreamImmediatelyOnDock);
+ mDreamsDisabledByAmbientModeSuppression = res.getBoolean(
+ com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
mNightMode = res.getInteger(
com.android.internal.R.integer.config_defaultNightMode);
mDefaultUiModeType = res.getInteger(
@@ -1827,10 +1836,14 @@ final class UiModeManagerService extends SystemService {
// Send the new configuration.
applyConfigurationExternallyLocked();
+ final boolean dreamsSuppressed = mDreamsDisabledByAmbientModeSuppression
+ && mLocalPowerManager.isAmbientDisplaySuppressed();
+
// If we did not start a dock app, then start dreaming if appropriate.
- if (category != null && !dockAppStarted && (mStartDreamImmediatelyOnDock
- || mWindowManager.isKeyguardShowingAndNotOccluded()
- || !mPowerManager.isInteractive())) {
+ if (category != null && !dockAppStarted && !dreamsSuppressed && (
+ mStartDreamImmediatelyOnDock
+ || mWindowManager.isKeyguardShowingAndNotOccluded()
+ || !mPowerManager.isInteractive())) {
mInjector.startDreamWhenDockedIfAppropriate(getContext());
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index fa751007198e..a778b573f225 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import android.annotation.NonNull;
@@ -57,6 +58,7 @@ import com.android.server.biometrics.sensors.SensorOverlays;
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import java.time.Clock;
import java.util.ArrayList;
import java.util.function.Supplier;
@@ -88,7 +90,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
private long mWaitForAuthKeyguard;
private long mWaitForAuthBp;
private long mIgnoreAuthFor;
+ private long mSideFpsLastAcquireStartTime;
private Runnable mAuthSuccessRunnable;
+ private final Clock mClock;
FingerprintAuthenticationClient(
@NonNull Context context,
@@ -112,7 +116,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
@Nullable ISidefpsController sidefpsController,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull Handler handler) {
+ @NonNull Handler handler,
+ @NonNull Clock clock) {
super(
context,
lazyDaemon,
@@ -154,6 +159,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mSkipWaitForPowerVendorAcquireMessage =
context.getResources().getInteger(
R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage);
+ mSideFpsLastAcquireStartTime = -1;
+ mClock = clock;
if (mSensorProps.isAnySidefpsType()) {
if (Build.isDebuggable()) {
@@ -235,8 +242,14 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
return;
}
delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
- Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps waiting for power for: "
- + delay + "ms");
+
+ if (mSideFpsLastAcquireStartTime != -1) {
+ delay = Math.max(0,
+ delay - (mClock.millis() - mSideFpsLastAcquireStartTime));
+ }
+
+ Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps "
+ + "waiting for power until: " + delay + "ms");
}
if (mHandler.hasMessages(MESSAGE_FINGER_UP)) {
@@ -260,13 +273,15 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo));
super.onAcquired(acquiredInfo, vendorCode);
if (mSensorProps.isAnySidefpsType()) {
+ if (acquiredInfo == FINGERPRINT_ACQUIRED_START) {
+ mSideFpsLastAcquireStartTime = mClock.millis();
+ }
final boolean shouldLookForVendor =
mSkipWaitForPowerAcquireMessage == FINGERPRINT_ACQUIRED_VENDOR;
final boolean acquireMessageMatch = acquiredInfo == mSkipWaitForPowerAcquireMessage;
final boolean vendorMessageMatch = vendorCode == mSkipWaitForPowerVendorAcquireMessage;
final boolean ignorePowerPress =
- (acquireMessageMatch && !shouldLookForVendor) || (shouldLookForVendor
- && acquireMessageMatch && vendorMessageMatch);
+ acquireMessageMatch && (!shouldLookForVendor || vendorMessageMatch);
if (ignorePowerPress) {
Slog.d(TAG, "(sideFPS) onFingerUp");
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 6f6c09b91a66..dabb2cf5819a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -47,6 +47,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.UserManager;
import android.util.Slog;
import android.util.SparseArray;
@@ -447,7 +448,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mBiometricContext, isStrongBiometric,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
- mSensors.get(sensorId).getSensorProperties(), mHandler);
+ mSensors.get(sensorId).getSensorProperties(), mHandler,
+ SystemClock.elapsedRealtimeClock());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 17215e5ae4ad..8f59ffd30bba 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -220,6 +220,11 @@ public class BrightnessTracker {
}
private void backgroundStart(float initialBrightness) {
+ synchronized (mDataCollectionLock) {
+ if (mStarted) {
+ return;
+ }
+ }
if (DEBUG) {
Slog.d(TAG, "Background start");
}
@@ -250,6 +255,11 @@ public class BrightnessTracker {
/** Stop listening for events */
void stop() {
+ synchronized (mDataCollectionLock) {
+ if (!mStarted) {
+ return;
+ }
+ }
if (DEBUG) {
Slog.d(TAG, "Stop");
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 1ae37bb5d66b..687d03d4f774 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -150,7 +150,7 @@ import javax.xml.datatype.DatatypeConfigurationException;
* <quirk>canSetBrightnessViaHwc</quirk>
* </quirks>
*
- * <autoBrightness>
+ * <autoBrightness enable="true">
* <brighteningLightDebounceMillis>
* 2000
* </brighteningLightDebounceMillis>
@@ -507,6 +507,11 @@ public class DisplayDeviceConfig {
private long mAutoBrightnessDarkeningLightDebounce =
INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE;
+ // This setting allows non-default displays to have autobrightness enabled.
+ private boolean mAutoBrightnessAvailable = false;
+ // This stores the raw value loaded from the config file - true if not written.
+ private boolean mDdcAutoBrightnessAvailable = true;
+
// 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.
@@ -1119,6 +1124,10 @@ public class DisplayDeviceConfig {
return mProximitySensor;
}
+ boolean isAutoBrightnessAvailable() {
+ return mAutoBrightnessAvailable;
+ }
+
/**
* @param quirkValue The quirk to test.
* @return {@code true} if the specified quirk is present in this configuration, {@code false}
@@ -1184,6 +1193,60 @@ public class DisplayDeviceConfig {
return mBrightnessLevelsNits;
}
+ /**
+ * @return Default peak refresh rate of the associated display
+ */
+ public int getDefaultPeakRefreshRate() {
+ return mContext.getResources().getInteger(R.integer.config_defaultPeakRefreshRate);
+ }
+
+ /**
+ * @return Default refresh rate of the associated display
+ */
+ public int getDefaultRefreshRate() {
+ return mContext.getResources().getInteger(R.integer.config_defaultRefreshRate);
+ }
+
+ /**
+ * @return An array of lower display brightness thresholds. This, in combination with lower
+ * ambient brightness thresholds help define buckets in which the refresh rate switching is not
+ * allowed
+ */
+ public int[] getLowDisplayBrightnessThresholds() {
+ return mContext.getResources().getIntArray(
+ R.array.config_brightnessThresholdsOfPeakRefreshRate);
+ }
+
+ /**
+ * @return An array of lower ambient brightness thresholds. This, in combination with lower
+ * display brightness thresholds help define buckets in which the refresh rate switching is not
+ * allowed
+ */
+ public int[] getLowAmbientBrightnessThresholds() {
+ return mContext.getResources().getIntArray(
+ R.array.config_ambientThresholdsOfPeakRefreshRate);
+ }
+
+ /**
+ * @return An array of high display brightness thresholds. This, in combination with high
+ * ambient brightness thresholds help define buckets in which the refresh rate switching is not
+ * allowed
+ */
+ public int[] getHighDisplayBrightnessThresholds() {
+ return mContext.getResources().getIntArray(
+ R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
+ }
+
+ /**
+ * @return An array of high ambient brightness thresholds. This, in combination with high
+ * display brightness thresholds help define buckets in which the refresh rate switching is not
+ * allowed
+ */
+ public int[] getHighAmbientBrightnessThresholds() {
+ return mContext.getResources().getIntArray(
+ R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -1271,6 +1334,8 @@ public class DisplayDeviceConfig {
+ mAutoBrightnessDarkeningLightDebounce
+ ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
+ ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+ + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ "}";
}
@@ -1349,6 +1414,7 @@ public class DisplayDeviceConfig {
loadBrightnessChangeThresholdsFromXml();
setProxSensorUnspecified();
loadAutoBrightnessConfigsFromConfigXml();
+ loadAutoBrightnessAvailableFromConfigXml();
mLoadedFrom = "<config.xml>";
}
@@ -1367,6 +1433,7 @@ public class DisplayDeviceConfig {
setSimpleMappingStrategyValues();
loadAmbientLightSensorFromConfigXml();
setProxSensorUnspecified();
+ loadAutoBrightnessAvailableFromConfigXml();
}
private void copyUninitializedValuesFromSecondaryConfig(DisplayConfiguration defaultConfig) {
@@ -1559,9 +1626,11 @@ public class DisplayDeviceConfig {
}
private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
- loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness());
- loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness());
- loadAutoBrightnessDisplayBrightnessMapping(config.getAutoBrightness());
+ final AutoBrightness autoBrightness = config.getAutoBrightness();
+ loadAutoBrightnessBrighteningLightDebounce(autoBrightness);
+ loadAutoBrightnessDarkeningLightDebounce(autoBrightness);
+ loadAutoBrightnessDisplayBrightnessMapping(autoBrightness);
+ loadEnableAutoBrightness(autoBrightness);
}
/**
@@ -1623,6 +1692,11 @@ public class DisplayDeviceConfig {
}
}
+ private void loadAutoBrightnessAvailableFromConfigXml() {
+ mAutoBrightnessAvailable = mContext.getResources().getBoolean(
+ R.bool.config_automatic_brightness_available);
+ }
+
private void loadBrightnessMapFromConfigXml() {
// Use the config.xml mapping
final Resources res = mContext.getResources();
@@ -2263,6 +2337,20 @@ public class DisplayDeviceConfig {
return levels;
}
+ private void loadEnableAutoBrightness(AutoBrightness autobrightness) {
+ // mDdcAutoBrightnessAvailable is initialised to true, so that we fallback to using the
+ // config.xml values if the autobrightness tag is not defined in the ddc file.
+ // Autobrightness can still be turned off globally via config_automatic_brightness_available
+ mDdcAutoBrightnessAvailable = true;
+ if (autobrightness != null) {
+ mDdcAutoBrightnessAvailable = autobrightness.getEnabled();
+ }
+
+ mAutoBrightnessAvailable = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_automatic_brightness_available)
+ && mDdcAutoBrightnessAvailable;
+ }
+
static class SensorData {
public String type;
public String name;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b153c1ba98e0..b8ff6ed93666 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1519,6 +1519,7 @@ public final class DisplayManagerService extends SystemService {
display.setUserDisabledHdrTypes(mUserDisabledHdrTypes);
}
if (isDefault) {
+ notifyDefaultDisplayDeviceUpdated(display);
recordStableDisplayStatsIfNeededLocked(display);
recordTopInsetLocked(display);
}
@@ -1612,6 +1613,11 @@ public final class DisplayManagerService extends SystemService {
mHandler.post(work);
}
final int displayId = display.getDisplayIdLocked();
+
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ notifyDefaultDisplayDeviceUpdated(display);
+ }
+
DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
if (dpc != null) {
dpc.onDisplayChanged();
@@ -1621,6 +1627,11 @@ public final class DisplayManagerService extends SystemService {
handleLogicalDisplayChangedLocked(display);
}
+ private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) {
+ mDisplayModeDirector.defaultDisplayDeviceUpdated(display.getPrimaryDisplayDeviceLocked()
+ .mDisplayDeviceConfig);
+ }
+
private void handleLogicalDisplayDeviceStateTransitionLocked(@NonNull LogicalDisplay display) {
final int displayId = display.getDisplayIdLocked();
final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
@@ -1759,9 +1770,13 @@ public final class DisplayManagerService extends SystemService {
if (displayDevice == null) {
return;
}
- mPersistentDataStore.setUserPreferredResolution(
- displayDevice, resolutionWidth, resolutionHeight);
- mPersistentDataStore.setUserPreferredRefreshRate(displayDevice, refreshRate);
+ try {
+ mPersistentDataStore.setUserPreferredResolution(
+ displayDevice, resolutionWidth, resolutionHeight);
+ mPersistentDataStore.setUserPreferredRefreshRate(displayDevice, refreshRate);
+ } finally {
+ mPersistentDataStore.saveIfNeeded();
+ }
}
private void setUserPreferredModeForDisplayLocked(int displayId, Display.Mode mode) {
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index ac72b1725432..912b1b2e8da2 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -79,6 +79,7 @@ import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
+import java.util.concurrent.Callable;
/**
* The DisplayModeDirector is responsible for determining what modes are allowed to be automatically
@@ -153,8 +154,10 @@ public class DisplayModeDirector {
mSupportedModesByDisplay = new SparseArray<>();
mDefaultModeByDisplay = new SparseArray<>();
mAppRequestObserver = new AppRequestObserver();
- mSettingsObserver = new SettingsObserver(context, handler);
mDisplayObserver = new DisplayObserver(context, handler);
+ mDeviceConfig = injector.getDeviceConfig();
+ mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
+ mSettingsObserver = new SettingsObserver(context, handler);
mBrightnessObserver = new BrightnessObserver(context, handler, injector);
mUdfpsObserver = new UdfpsObserver();
final BallotBox ballotBox = (displayId, priority, vote) -> {
@@ -164,10 +167,8 @@ public class DisplayModeDirector {
};
mSensorObserver = new SensorObserver(context, ballotBox, injector);
mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox);
- mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(),
mDeviceConfigDisplaySettings);
- mDeviceConfig = injector.getDeviceConfig();
mAlwaysRespectAppRequest = false;
}
@@ -518,6 +519,15 @@ public class DisplayModeDirector {
}
/**
+ * A utility to make this class aware of the new display configs whenever the default display is
+ * changed
+ */
+ public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
+ mSettingsObserver.setRefreshRates(displayDeviceConfig);
+ mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig);
+ }
+
+ /**
* When enabled the app requested display mode is always selected and all
* other votes will be ignored. This is used for testing purposes.
*/
@@ -1132,10 +1142,19 @@ public class DisplayModeDirector {
SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
super(handler);
mContext = context;
- mDefaultPeakRefreshRate = (float) context.getResources().getInteger(
- R.integer.config_defaultPeakRefreshRate);
+ setRefreshRates(/* displayDeviceConfig= */ null);
+ }
+
+ /**
+ * This is used to update the refresh rate configs from the DeviceConfig, which
+ * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
+ */
+ public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig) {
+ setDefaultPeakRefreshRate(displayDeviceConfig);
mDefaultRefreshRate =
- (float) context.getResources().getInteger(R.integer.config_defaultRefreshRate);
+ (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRate)
+ : (float) displayDeviceConfig.getDefaultRefreshRate();
}
public void observe() {
@@ -1196,6 +1215,23 @@ public class DisplayModeDirector {
}
}
+ private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig) {
+ Float defaultPeakRefreshRate = null;
+ try {
+ defaultPeakRefreshRate =
+ mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
+ } catch (Exception exception) {
+ // Do nothing
+ }
+ if (defaultPeakRefreshRate == null) {
+ defaultPeakRefreshRate =
+ (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
+ R.integer.config_defaultPeakRefreshRate)
+ : (float) displayDeviceConfig.getDefaultPeakRefreshRate();
+ }
+ mDefaultPeakRefreshRate = defaultPeakRefreshRate;
+ }
+
private void updateLowPowerModeSettingLocked() {
boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0;
@@ -1508,12 +1544,31 @@ public class DisplayModeDirector {
mContext = context;
mHandler = handler;
mInjector = injector;
+ updateBlockingZoneThresholds(/* displayDeviceConfig= */ null);
+ mRefreshRateInHighZone = context.getResources().getInteger(
+ R.integer.config_fixedRefreshRateInHighZone);
+ }
- mLowDisplayBrightnessThresholds = context.getResources().getIntArray(
- R.array.config_brightnessThresholdsOfPeakRefreshRate);
- mLowAmbientBrightnessThresholds = context.getResources().getIntArray(
- R.array.config_ambientThresholdsOfPeakRefreshRate);
-
+ /**
+ * This is used to update the blocking zone thresholds from the DeviceConfig, which
+ * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
+ */
+ public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig) {
+ loadLowBrightnessThresholds(displayDeviceConfig);
+ loadHighBrightnessThresholds(displayDeviceConfig);
+ }
+
+ private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) {
+ mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
+ () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
+ () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
+ R.array.config_brightnessThresholdsOfPeakRefreshRate,
+ displayDeviceConfig);
+ mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
+ () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
+ () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
+ R.array.config_ambientThresholdsOfPeakRefreshRate,
+ displayDeviceConfig);
if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
throw new RuntimeException("display low brightness threshold array and ambient "
+ "brightness threshold array have different length: "
@@ -1522,11 +1577,19 @@ public class DisplayModeDirector {
+ ", ambientBrightnessThresholds="
+ Arrays.toString(mLowAmbientBrightnessThresholds));
}
+ }
- mHighDisplayBrightnessThresholds = context.getResources().getIntArray(
- R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
- mHighAmbientBrightnessThresholds = context.getResources().getIntArray(
- R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+ private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) {
+ mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
+ () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
+ () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
+ R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
+ displayDeviceConfig);
+ mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
+ () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
+ () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
+ R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
+ displayDeviceConfig);
if (mHighDisplayBrightnessThresholds.length
!= mHighAmbientBrightnessThresholds.length) {
throw new RuntimeException("display high brightness threshold array and ambient "
@@ -1536,8 +1599,32 @@ public class DisplayModeDirector {
+ ", ambientBrightnessThresholds="
+ Arrays.toString(mHighAmbientBrightnessThresholds));
}
- mRefreshRateInHighZone = context.getResources().getInteger(
- R.integer.config_fixedRefreshRateInHighZone);
+ }
+
+ private int[] loadBrightnessThresholds(
+ Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable,
+ Callable<int[]> loadFromDisplayDeviceConfigCallable,
+ int brightnessThresholdOfFixedRefreshRateKey,
+ DisplayDeviceConfig displayDeviceConfig) {
+ int[] brightnessThresholds = null;
+ try {
+ brightnessThresholds =
+ loadFromDeviceConfigDisplaySettingsCallable.call();
+ } catch (Exception exception) {
+ // Do nothing
+ }
+ if (brightnessThresholds == null) {
+ try {
+ brightnessThresholds =
+ (displayDeviceConfig == null) ? mContext.getResources().getIntArray(
+ brightnessThresholdOfFixedRefreshRateKey)
+ : loadFromDisplayDeviceConfigCallable.call();
+ } catch (Exception e) {
+ Slog.e(TAG, "Unexpectedly failed to load display brightness threshold");
+ e.printStackTrace();
+ }
+ }
+ return brightnessThresholds;
}
/**
@@ -1590,7 +1677,6 @@ public class DisplayModeDirector {
mLowAmbientBrightnessThresholds = lowAmbientBrightnessThresholds;
}
-
int[] highDisplayBrightnessThresholds =
mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds();
int[] highAmbientBrightnessThresholds =
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 458cf1a31b97..95dc23fc3120 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -558,13 +558,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
- // Check the setting, but also verify that it is the default display. Only the default
- // display has an automatic brightness controller running.
- // TODO: b/179021925 - Fix to work with multiple displays
- mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
- com.android.internal.R.bool.config_automatic_brightness_available)
- && mDisplayId == Display.DEFAULT_DISPLAY;
-
mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
@@ -938,6 +931,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
private void setUpAutoBrightness(Resources resources, Handler handler) {
+ mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
+
if (!mUseSoftwareAutoBrightnessConfig) {
return;
}
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index b9a0738d15c4..a11f1721a4b9 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -75,6 +75,11 @@ import java.util.Objects;
* &lt;/brightness-curve>
* &lt;/brightness-configuration>
* &lt;/brightness-configurations>
+ * &lt;display-mode>0&lt;
+ * &lt;resolution-width>1080&lt;/resolution-width>
+ * &lt;resolution-height>1920&lt;/resolution-height>
+ * &lt;refresh-rate>60&lt;/refresh-rate>
+ * &lt;/display-mode>
* &lt;/display>
* &lt;/display-states>
* &lt;stable-device-values>
@@ -121,6 +126,10 @@ final class PersistentDataStore {
private static final String ATTR_PACKAGE_NAME = "package-name";
private static final String ATTR_TIME_STAMP = "timestamp";
+ private static final String TAG_RESOLUTION_WIDTH = "resolution-width";
+ private static final String TAG_RESOLUTION_HEIGHT = "resolution-height";
+ private static final String TAG_REFRESH_RATE = "refresh-rate";
+
// Remembered Wifi display devices.
private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
@@ -696,6 +705,18 @@ final class PersistentDataStore {
case TAG_BRIGHTNESS_CONFIGURATIONS:
mDisplayBrightnessConfigurations.loadFromXml(parser);
break;
+ case TAG_RESOLUTION_WIDTH:
+ String width = parser.nextText();
+ mWidth = Integer.parseInt(width);
+ break;
+ case TAG_RESOLUTION_HEIGHT:
+ String height = parser.nextText();
+ mHeight = Integer.parseInt(height);
+ break;
+ case TAG_REFRESH_RATE:
+ String refreshRate = parser.nextText();
+ mRefreshRate = Float.parseFloat(refreshRate);
+ break;
}
}
}
@@ -712,6 +733,18 @@ final class PersistentDataStore {
serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
mDisplayBrightnessConfigurations.saveToXml(serializer);
serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
+
+ serializer.startTag(null, TAG_RESOLUTION_WIDTH);
+ serializer.text(Integer.toString(mWidth));
+ serializer.endTag(null, TAG_RESOLUTION_WIDTH);
+
+ serializer.startTag(null, TAG_RESOLUTION_HEIGHT);
+ serializer.text(Integer.toString(mHeight));
+ serializer.endTag(null, TAG_RESOLUTION_HEIGHT);
+
+ serializer.startTag(null, TAG_REFRESH_RATE);
+ serializer.text(Float.toString(mRefreshRate));
+ serializer.endTag(null, TAG_REFRESH_RATE);
}
public void dump(final PrintWriter pw, final String prefix) {
@@ -719,6 +752,8 @@ final class PersistentDataStore {
pw.println(prefix + "BrightnessValue=" + mBrightness);
pw.println(prefix + "DisplayBrightnessConfigurations: ");
mDisplayBrightnessConfigurations.dump(pw, prefix);
+ pw.println(prefix + "Resolution=" + mWidth + " " + mHeight);
+ pw.println(prefix + "RefreshRate=" + mRefreshRate);
}
}
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index b8af1bfcc254..b11a06eda025 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -34,13 +34,13 @@ import android.os.UserHandle;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamService;
import android.util.Slog;
-import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
import java.util.NoSuchElementException;
/**
@@ -60,9 +60,6 @@ final class DreamController {
private final Context mContext;
private final Handler mHandler;
private final Listener mListener;
- private final IWindowManager mIWindowManager;
- private long mDreamStartTime;
- private String mSavedStopReason;
private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -73,27 +70,20 @@ final class DreamController {
private DreamRecord mCurrentDream;
- private final Runnable mStopUnconnectedDreamRunnable = new Runnable() {
- @Override
- public void run() {
- if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) {
- Slog.w(TAG, "Bound dream did not connect in the time allotted");
- stopDream(true /*immediate*/, "slow to connect");
- }
- }
- };
+ // Whether a dreaming started intent has been broadcast.
+ private boolean mSentStartBroadcast = false;
- private final Runnable mStopStubbornDreamRunnable = () -> {
- Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted");
- stopDream(true /*immediate*/, "slow to finish");
- mSavedStopReason = null;
- };
+ // When a new dream is started and there is an existing dream, the existing dream is allowed to
+ // live a little longer until the new dream is started, for a smoother transition. This dream is
+ // stopped as soon as the new dream is started, and this list is cleared. Usually there should
+ // only be one previous dream while waiting for a new dream to start, but we store a list to
+ // proof the edge case of multiple previous dreams.
+ private final ArrayList<DreamRecord> mPreviousDreams = new ArrayList<>();
public DreamController(Context context, Handler handler, Listener listener) {
mContext = context;
mHandler = handler;
mListener = listener;
- mIWindowManager = WindowManagerGlobal.getWindowManagerService();
mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mCloseNotificationShadeIntent.putExtra("reason", "dream");
}
@@ -109,18 +99,17 @@ final class DreamController {
pw.println(" mUserId=" + mCurrentDream.mUserId);
pw.println(" mBound=" + mCurrentDream.mBound);
pw.println(" mService=" + mCurrentDream.mService);
- pw.println(" mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast);
pw.println(" mWakingGently=" + mCurrentDream.mWakingGently);
} else {
pw.println(" mCurrentDream: null");
}
+
+ pw.println(" mSentStartBroadcast=" + mSentStartBroadcast);
}
public void startDream(Binder token, ComponentName name,
boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock,
ComponentName overlayComponentName, String reason) {
- stopDream(true /*immediate*/, "starting new dream");
-
Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream");
try {
// Close the notification shade. No need to send to all, but better to be explicit.
@@ -130,9 +119,12 @@ final class DreamController {
+ ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze
+ ", userId=" + userId + ", reason='" + reason + "'");
+ if (mCurrentDream != null) {
+ mPreviousDreams.add(mCurrentDream);
+ }
mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock);
- mDreamStartTime = SystemClock.elapsedRealtime();
+ mCurrentDream.mDreamStartTime = SystemClock.elapsedRealtime();
MetricsLogger.visible(mContext,
mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
@@ -155,31 +147,49 @@ final class DreamController {
}
mCurrentDream.mBound = true;
- mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT);
+ mHandler.postDelayed(mCurrentDream.mStopUnconnectedDreamRunnable,
+ DREAM_CONNECTION_TIMEOUT);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
+ /**
+ * Stops dreaming.
+ *
+ * The current dream, if any, and any unstopped previous dreams are stopped. The device stops
+ * dreaming.
+ */
public void stopDream(boolean immediate, String reason) {
- if (mCurrentDream == null) {
+ stopPreviousDreams();
+ stopDreamInstance(immediate, reason, mCurrentDream);
+ }
+
+ /**
+ * Stops the given dream instance.
+ *
+ * The device may still be dreaming afterwards if there are other dreams running.
+ */
+ private void stopDreamInstance(boolean immediate, String reason, DreamRecord dream) {
+ if (dream == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream");
try {
if (!immediate) {
- if (mCurrentDream.mWakingGently) {
+ if (dream.mWakingGently) {
return; // already waking gently
}
- if (mCurrentDream.mService != null) {
+ if (dream.mService != null) {
// Give the dream a moment to wake up and finish itself gently.
- mCurrentDream.mWakingGently = true;
+ dream.mWakingGently = true;
try {
- mSavedStopReason = reason;
- mCurrentDream.mService.wakeUp();
- mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT);
+ dream.mStopReason = reason;
+ dream.mService.wakeUp();
+ mHandler.postDelayed(dream.mStopStubbornDreamRunnable,
+ DREAM_FINISH_TIMEOUT);
return;
} catch (RemoteException ex) {
// oh well, we tried, finish immediately instead
@@ -187,54 +197,73 @@ final class DreamController {
}
}
- final DreamRecord oldDream = mCurrentDream;
- mCurrentDream = null;
- Slog.i(TAG, "Stopping dream: name=" + oldDream.mName
- + ", isPreviewMode=" + oldDream.mIsPreviewMode
- + ", canDoze=" + oldDream.mCanDoze
- + ", userId=" + oldDream.mUserId
+ Slog.i(TAG, "Stopping dream: name=" + dream.mName
+ + ", isPreviewMode=" + dream.mIsPreviewMode
+ + ", canDoze=" + dream.mCanDoze
+ + ", userId=" + dream.mUserId
+ ", reason='" + reason + "'"
- + (mSavedStopReason == null ? "" : "(from '" + mSavedStopReason + "')"));
+ + (dream.mStopReason == null ? "" : "(from '"
+ + dream.mStopReason + "')"));
MetricsLogger.hidden(mContext,
- oldDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
+ dream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
MetricsLogger.histogram(mContext,
- oldDream.mCanDoze ? "dozing_minutes" : "dreaming_minutes" ,
- (int) ((SystemClock.elapsedRealtime() - mDreamStartTime) / (1000L * 60L)));
+ dream.mCanDoze ? "dozing_minutes" : "dreaming_minutes",
+ (int) ((SystemClock.elapsedRealtime() - dream.mDreamStartTime) / (1000L
+ * 60L)));
- mHandler.removeCallbacks(mStopUnconnectedDreamRunnable);
- mHandler.removeCallbacks(mStopStubbornDreamRunnable);
- mSavedStopReason = null;
+ mHandler.removeCallbacks(dream.mStopUnconnectedDreamRunnable);
+ mHandler.removeCallbacks(dream.mStopStubbornDreamRunnable);
- if (oldDream.mSentStartBroadcast) {
- mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
- }
-
- if (oldDream.mService != null) {
+ if (dream.mService != null) {
try {
- oldDream.mService.detach();
+ dream.mService.detach();
} catch (RemoteException ex) {
// we don't care; this thing is on the way out
}
try {
- oldDream.mService.asBinder().unlinkToDeath(oldDream, 0);
+ dream.mService.asBinder().unlinkToDeath(dream, 0);
} catch (NoSuchElementException ex) {
// don't care
}
- oldDream.mService = null;
+ dream.mService = null;
}
- if (oldDream.mBound) {
- mContext.unbindService(oldDream);
+ if (dream.mBound) {
+ mContext.unbindService(dream);
}
- oldDream.releaseWakeLockIfNeeded();
+ dream.releaseWakeLockIfNeeded();
+
+ // Current dream stopped, device no longer dreaming.
+ if (dream == mCurrentDream) {
+ mCurrentDream = null;
+
+ if (mSentStartBroadcast) {
+ mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
+ }
- mHandler.post(() -> mListener.onDreamStopped(oldDream.mToken));
+ mListener.onDreamStopped(dream.mToken);
+ }
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
+ /**
+ * Stops all previous dreams, if any.
+ */
+ private void stopPreviousDreams() {
+ if (mPreviousDreams.isEmpty()) {
+ return;
+ }
+
+ // Using an iterator because mPreviousDreams is modified while the iteration is in process.
+ for (final Iterator<DreamRecord> it = mPreviousDreams.iterator(); it.hasNext(); ) {
+ stopDreamInstance(true /*immediate*/, "stop previous dream", it.next());
+ it.remove();
+ }
+ }
+
private void attach(IDreamService service) {
try {
service.asBinder().linkToDeath(mCurrentDream, 0);
@@ -248,9 +277,9 @@ final class DreamController {
mCurrentDream.mService = service;
- if (!mCurrentDream.mIsPreviewMode) {
+ if (!mCurrentDream.mIsPreviewMode && !mSentStartBroadcast) {
mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
- mCurrentDream.mSentStartBroadcast = true;
+ mSentStartBroadcast = true;
}
}
@@ -272,10 +301,35 @@ final class DreamController {
public boolean mBound;
public boolean mConnected;
public IDreamService mService;
- public boolean mSentStartBroadcast;
-
+ private String mStopReason;
+ private long mDreamStartTime;
public boolean mWakingGently;
+ private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded;
+ private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded;
+
+ private final Runnable mStopUnconnectedDreamRunnable = () -> {
+ if (mBound && !mConnected) {
+ Slog.w(TAG, "Bound dream did not connect in the time allotted");
+ stopDream(true /*immediate*/, "slow to connect" /*reason*/);
+ }
+ };
+
+ private final Runnable mStopStubbornDreamRunnable = () -> {
+ Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted");
+ stopDream(true /*immediate*/, "slow to finish" /*reason*/);
+ mStopReason = null;
+ };
+
+ private final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() {
+ // May be called on any thread.
+ @Override
+ public void sendResult(Bundle data) {
+ mHandler.post(mStopPreviousDreamsIfNeeded);
+ mHandler.post(mReleaseWakeLockIfNeeded);
+ }
+ };
+
DreamRecord(Binder token, ComponentName name, boolean isPreviewMode,
boolean canDoze, int userId, PowerManager.WakeLock wakeLock) {
mToken = token;
@@ -286,7 +340,9 @@ final class DreamController {
mWakeLock = wakeLock;
// Hold the lock while we're waiting for the service to connect and start dreaming.
// Released after the service has started dreaming, we stop dreaming, or it timed out.
- mWakeLock.acquire();
+ if (mWakeLock != null) {
+ mWakeLock.acquire();
+ }
mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000);
}
@@ -326,6 +382,12 @@ final class DreamController {
});
}
+ void stopPreviousDreamsIfNeeded() {
+ if (mCurrentDream == DreamRecord.this) {
+ stopPreviousDreams();
+ }
+ }
+
void releaseWakeLockIfNeeded() {
if (mWakeLock != null) {
mWakeLock.release();
@@ -333,15 +395,5 @@ final class DreamController {
mHandler.removeCallbacks(mReleaseWakeLockIfNeeded);
}
}
-
- final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded;
-
- final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() {
- // May be called on any thread.
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- mHandler.post(mReleaseWakeLockIfNeeded);
- }
- };
}
}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7964fd550266..5589673973c3 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -246,8 +246,8 @@ public final class DreamManagerService extends SystemService {
// Because napping could cause the screen to turn off immediately if the dream
// cannot be started, we keep one eye open and gently poke user activity.
long time = SystemClock.uptimeMillis();
- mPowerManager.userActivity(time, true /*noChangeLights*/);
- mPowerManager.nap(time);
+ mPowerManager.userActivity(time, /* noChangeLights= */ true);
+ mPowerManagerInternal.nap(time, /* allowWake= */ true);
}
private void requestAwakenInternal(String reason) {
@@ -493,8 +493,6 @@ public final class DreamManagerService extends SystemService {
return;
}
- stopDreamLocked(true /*immediate*/, "starting new dream");
-
Slog.i(TAG, "Entering dreamland.");
mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze);
@@ -637,7 +635,7 @@ public final class DreamManagerService extends SystemService {
@Nullable FileDescriptor err,
@NonNull String[] args, @Nullable ShellCallback callback,
@NonNull ResultReceiver resultReceiver) throws RemoteException {
- new DreamShellCommand(DreamManagerService.this, mPowerManager)
+ new DreamShellCommand(DreamManagerService.this)
.exec(this, in, out, err, args, callback, resultReceiver);
}
diff --git a/services/core/java/com/android/server/dreams/DreamShellCommand.java b/services/core/java/com/android/server/dreams/DreamShellCommand.java
index eae7e80a89f1..ab84ae4c08a2 100644
--- a/services/core/java/com/android/server/dreams/DreamShellCommand.java
+++ b/services/core/java/com/android/server/dreams/DreamShellCommand.java
@@ -18,10 +18,8 @@ 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;
@@ -34,11 +32,9 @@ 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) {
+ DreamShellCommand(@NonNull DreamManagerService service) {
mService = service;
- mPowerManager = powerManager;
}
@Override
@@ -67,8 +63,6 @@ public class DreamShellCommand extends ShellCommand {
}
private int startDreaming() {
- mPowerManager.wakeUp(SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_PLUGGED_IN, "shell:cmd:android.service.dreams:DREAM");
mService.requestStartDreamFromShell();
return 0;
}
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index 6759d79eedca..9a190316f4eb 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -32,7 +32,6 @@ import android.os.Handler;
import android.os.PowerWhitelistManager;
import android.os.UserHandle;
import android.text.TextUtils;
-import android.util.EventLog;
import android.util.Log;
import android.view.KeyEvent;
@@ -118,12 +117,6 @@ final class MediaButtonReceiverHolder {
int componentType = getComponentType(pendingIntent);
ComponentName componentName = getComponentName(pendingIntent, componentType);
if (componentName != null) {
- if (!TextUtils.equals(componentName.getPackageName(), sessionPackageName)) {
- EventLog.writeEvent(0x534e4554, "238177121", -1, ""); // SafetyNet logging
- throw new IllegalArgumentException("ComponentName does not belong to "
- + "sessionPackageName. sessionPackageName = " + sessionPackageName
- + ", ComponentName pkg = " + componentName.getPackageName());
- }
return new MediaButtonReceiverHolder(userId, pendingIntent, componentName,
componentType);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index b8131a8ee5b5..604e8f3949f4 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -52,8 +52,6 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.EventLog;
import android.util.Log;
import android.view.KeyEvent;
@@ -940,14 +938,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
@Override
public void setMediaButtonReceiver(PendingIntent pi, String sessionPackageName)
throws RemoteException {
- //mPackageName has been verified in MediaSessionService.enforcePackageName().
- if (!TextUtils.equals(sessionPackageName, mPackageName)) {
- EventLog.writeEvent(0x534e4554, "238177121", -1, ""); // SafetyNet logging
- throw new IllegalArgumentException("sessionPackageName name does not match "
- + "package name provided to MediaSessionRecord. sessionPackageName = "
- + sessionPackageName + ", pkg = "
- + mPackageName);
- }
final long token = Binder.clearCallingIdentity();
try {
if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
@@ -966,15 +956,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException {
final long token = Binder.clearCallingIdentity();
try {
- //mPackageName has been verified in MediaSessionService.enforcePackageName().
- if (receiver != null && !TextUtils.equals(
- mPackageName, receiver.getPackageName())) {
- EventLog.writeEvent(0x534e4554, "238177121", -1, ""); // SafetyNet logging
- throw new IllegalArgumentException("receiver does not belong to "
- + "package name provided to MediaSessionRecord. Pkg = " + mPackageName
- + ", Receiver Pkg = " + receiver.getPackageName());
- }
-
if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
!= 0) {
return;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fd1fdce4cf77..a55b11838c7b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -61,6 +61,7 @@ import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELECOM;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.MATCH_ALL;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -10633,10 +10634,18 @@ public class NotificationManagerService extends SystemService {
private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
ArrayMap<Pair<ComponentName, Integer>, NotificationListenerFilter>
mRequestedNotificationListeners = new ArrayMap<>();
+ private final boolean mIsHeadlessSystemUserMode;
public NotificationListeners(Context context, Object lock, UserProfiles userProfiles,
IPackageManager pm) {
+ this(context, lock, userProfiles, pm, UserManager.isHeadlessSystemUserMode());
+ }
+
+ @VisibleForTesting
+ public NotificationListeners(Context context, Object lock, UserProfiles userProfiles,
+ IPackageManager pm, boolean isHeadlessSystemUserMode) {
super(context, lock, userProfiles, pm);
+ this.mIsHeadlessSystemUserMode = isHeadlessSystemUserMode;
}
@Override
@@ -10661,10 +10670,16 @@ public class NotificationManagerService extends SystemService {
if (TextUtils.isEmpty(listeners[i])) {
continue;
}
+ int packageQueryFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+ // In the headless system user mode, packages might not be installed for the
+ // system user. Match packages for any user since apps can be installed only for
+ // non-system users and would be considering uninstalled for the system user.
+ if (mIsHeadlessSystemUserMode) {
+ packageQueryFlags += MATCH_ANY_USER;
+ }
ArraySet<ComponentName> approvedListeners =
- this.queryPackageForServices(listeners[i],
- MATCH_DIRECT_BOOT_AWARE
- | MATCH_DIRECT_BOOT_UNAWARE, USER_SYSTEM);
+ this.queryPackageForServices(listeners[i], packageQueryFlags,
+ USER_SYSTEM);
for (int k = 0; k < approvedListeners.size(); k++) {
ComponentName cn = approvedListeners.valueAt(k);
addDefaultComponentOrPackage(cn.flattenToString());
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 014d580e520f..18c29fa9f3a2 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -644,8 +644,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
Permission bp = mRegistry.getPermission(info.name);
added = bp == null;
int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
+ enforcePermissionCapLocked(info, tree);
if (added) {
- enforcePermissionCapLocked(info, tree);
bp = new Permission(info.name, tree.getPackageName(), Permission.TYPE_DYNAMIC);
} else if (!bp.isDynamic()) {
throw new SecurityException("Not allowed to modify non-dynamic permission "
diff --git a/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java b/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java
index aad7b1457b3c..7440fc75bf51 100644
--- a/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java
+++ b/services/core/java/com/android/server/power/AmbientDisplaySuppressionController.java
@@ -40,13 +40,24 @@ import java.util.Set;
public class AmbientDisplaySuppressionController {
private static final String TAG = "AmbientDisplaySuppressionController";
- private final Context mContext;
private final Set<Pair<String, Integer>> mSuppressionTokens;
+ private final AmbientDisplaySuppressionChangedCallback mCallback;
private IStatusBarService mStatusBarService;
- AmbientDisplaySuppressionController(Context context) {
- mContext = requireNonNull(context);
+ /** Interface to get a list of available logical devices. */
+ interface AmbientDisplaySuppressionChangedCallback {
+ /**
+ * Called when the suppression state changes.
+ *
+ * @param isSuppressed Whether ambient is suppressed.
+ */
+ void onSuppressionChanged(boolean isSuppressed);
+ }
+
+ AmbientDisplaySuppressionController(
+ @NonNull AmbientDisplaySuppressionChangedCallback callback) {
mSuppressionTokens = Collections.synchronizedSet(new ArraySet<>());
+ mCallback = requireNonNull(callback);
}
/**
@@ -58,6 +69,7 @@ public class AmbientDisplaySuppressionController {
*/
public void suppress(@NonNull String token, int callingUid, boolean suppress) {
Pair<String, Integer> suppressionToken = Pair.create(requireNonNull(token), callingUid);
+ final boolean wasSuppressed = isSuppressed();
if (suppress) {
mSuppressionTokens.add(suppressionToken);
@@ -65,9 +77,14 @@ public class AmbientDisplaySuppressionController {
mSuppressionTokens.remove(suppressionToken);
}
+ final boolean isSuppressed = isSuppressed();
+ if (isSuppressed != wasSuppressed) {
+ mCallback.onSuppressionChanged(isSuppressed);
+ }
+
try {
synchronized (mSuppressionTokens) {
- getStatusBar().suppressAmbientDisplay(isSuppressed());
+ getStatusBar().suppressAmbientDisplay(isSuppressed);
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to suppress ambient display", e);
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 9fe53fbfaf25..431cf3861804 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -229,8 +229,8 @@ public class PowerGroup {
}
}
- boolean dreamLocked(long eventTime, int uid) {
- if (eventTime < mLastWakeTime || mWakefulness != WAKEFULNESS_AWAKE) {
+ boolean dreamLocked(long eventTime, int uid, boolean allowWake) {
+ if (eventTime < mLastWakeTime || (!allowWake && mWakefulness != WAKEFULNESS_AWAKE)) {
return false;
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index f200564a9007..725fb3fec616 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -127,6 +127,7 @@ import com.android.server.am.BatteryStatsService;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.power.AmbientDisplaySuppressionController.AmbientDisplaySuppressionChangedCallback;
import com.android.server.power.batterysaver.BatterySaverController;
import com.android.server.power.batterysaver.BatterySaverPolicy;
import com.android.server.power.batterysaver.BatterySaverStateMachine;
@@ -467,6 +468,9 @@ public final class PowerManagerService extends SystemService
// True if the device should wake up when plugged or unplugged.
private boolean mWakeUpWhenPluggedOrUnpluggedConfig;
+ // True if the device should keep dreaming when undocked.
+ private boolean mKeepDreamingWhenUndockingConfig;
+
// True if the device should wake up when plugged or unplugged in theater mode.
private boolean mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig;
@@ -504,6 +508,9 @@ public final class PowerManagerService extends SystemService
// effectively and terminate the dream. Use -1 to disable this safety feature.
private int mDreamsBatteryLevelDrainCutoffConfig;
+ // Whether dreams should be disabled when ambient mode is suppressed.
+ private boolean mDreamsDisabledByAmbientModeSuppressionConfig;
+
// True if dreams are enabled by the user.
private boolean mDreamsEnabledSetting;
@@ -961,8 +968,8 @@ public final class PowerManagerService extends SystemService
}
AmbientDisplaySuppressionController createAmbientDisplaySuppressionController(
- Context context) {
- return new AmbientDisplaySuppressionController(context);
+ @NonNull AmbientDisplaySuppressionChangedCallback callback) {
+ return new AmbientDisplaySuppressionController(callback);
}
InattentiveSleepWarningController createInattentiveSleepWarningController() {
@@ -1041,7 +1048,8 @@ public final class PowerManagerService extends SystemService
mConstants = new Constants(mHandler);
mAmbientDisplayConfiguration = mInjector.createAmbientDisplayConfiguration(context);
mAmbientDisplaySuppressionController =
- mInjector.createAmbientDisplaySuppressionController(context);
+ mInjector.createAmbientDisplaySuppressionController(
+ mAmbientSuppressionChangedCallback);
mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock);
mFaceDownDetector = new FaceDownDetector(this::onFlip);
mScreenUndimDetector = new ScreenUndimDetector();
@@ -1374,6 +1382,8 @@ public final class PowerManagerService extends SystemService
com.android.internal.R.bool.config_powerDecoupleInteractiveModeFromDisplay);
mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean(
com.android.internal.R.bool.config_unplugTurnsOnScreen);
+ mKeepDreamingWhenUndockingConfig = resources.getBoolean(
+ com.android.internal.R.bool.config_keepDreamingWhenUndocking);
mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig = resources.getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug);
mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean(
@@ -1398,6 +1408,8 @@ public final class PowerManagerService extends SystemService
com.android.internal.R.integer.config_dreamsBatteryLevelMinimumWhenNotPowered);
mDreamsBatteryLevelDrainCutoffConfig = resources.getInteger(
com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff);
+ mDreamsDisabledByAmbientModeSuppressionConfig = resources.getBoolean(
+ com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
mDozeAfterScreenOff = resources.getBoolean(
com.android.internal.R.bool.config_dozeAfterScreenOffByDefault);
mMinimumScreenOffTimeoutConfig = resources.getInteger(
@@ -1919,6 +1931,13 @@ public final class PowerManagerService extends SystemService
}
}
+ private void napInternal(long eventTime, int uid, boolean allowWake) {
+ synchronized (mLock) {
+ dreamPowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
+ eventTime, uid, allowWake);
+ }
+ }
+
private void onUserAttention() {
synchronized (mLock) {
if (userActivityNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
@@ -2034,7 +2053,8 @@ public final class PowerManagerService extends SystemService
}
@GuardedBy("mLock")
- private boolean dreamPowerGroupLocked(PowerGroup powerGroup, long eventTime, int uid) {
+ private boolean dreamPowerGroupLocked(PowerGroup powerGroup, long eventTime, int uid,
+ boolean allowWake) {
if (DEBUG_SPEW) {
Slog.d(TAG, "dreamPowerGroup: groupId=" + powerGroup.getGroupId() + ", eventTime="
+ eventTime + ", uid=" + uid);
@@ -2042,7 +2062,7 @@ public final class PowerManagerService extends SystemService
if (!mBootCompleted || !mSystemReady) {
return false;
}
- return powerGroup.dreamLocked(eventTime, uid);
+ return powerGroup.dreamLocked(eventTime, uid, allowWake);
}
@GuardedBy("mLock")
@@ -2487,6 +2507,14 @@ public final class PowerManagerService extends SystemService
return false;
}
+ // Don't wake when undocking while dreaming if configured not to.
+ if (mKeepDreamingWhenUndockingConfig
+ && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING
+ && wasPowered && !mIsPowered
+ && oldPlugType == BatteryManager.BATTERY_PLUGGED_DOCK) {
+ return false;
+ }
+
// Don't wake when undocked from wireless charger.
// See WirelessChargerDetector for justification.
if (wasPowered && !mIsPowered
@@ -3086,7 +3114,8 @@ public final class PowerManagerService extends SystemService
changed = sleepPowerGroupLocked(powerGroup, time,
PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, Process.SYSTEM_UID);
} else if (shouldNapAtBedTimeLocked()) {
- changed = dreamPowerGroupLocked(powerGroup, time, Process.SYSTEM_UID);
+ changed = dreamPowerGroupLocked(powerGroup, time,
+ Process.SYSTEM_UID, /* allowWake= */ false);
} else {
changed = dozePowerGroupLocked(powerGroup, time,
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, Process.SYSTEM_UID);
@@ -3307,7 +3336,7 @@ public final class PowerManagerService extends SystemService
}
// Doze has ended or will be stopped. Update the power state.
- sleepPowerGroupLocked(powerGroup, now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
+ sleepPowerGroupLocked(powerGroup, now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
Process.SYSTEM_UID);
}
}
@@ -3318,12 +3347,32 @@ public final class PowerManagerService extends SystemService
}
}
+ @GuardedBy("mLock")
+ private void onDreamSuppressionChangedLocked(final boolean isSuppressed) {
+ if (!mDreamsDisabledByAmbientModeSuppressionConfig) {
+ return;
+ }
+ if (!isSuppressed && mIsPowered && mDreamsSupportedConfig && mDreamsEnabledSetting
+ && shouldNapAtBedTimeLocked() && isItBedTimeYetLocked(
+ mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP))) {
+ napInternal(SystemClock.uptimeMillis(), Process.SYSTEM_UID, /* allowWake= */ true);
+ } else if (isSuppressed) {
+ mDirty |= DIRTY_SETTINGS;
+ updatePowerStateLocked();
+ }
+ }
+
+
/**
* Returns true if the {@code groupId} is allowed to dream in its current state.
*/
@GuardedBy("mLock")
private boolean canDreamLocked(final PowerGroup powerGroup) {
+ final boolean dreamsSuppressed = mDreamsDisabledByAmbientModeSuppressionConfig
+ && mAmbientDisplaySuppressionController.isSuppressed();
+
if (!mBootCompleted
+ || dreamsSuppressed
|| getGlobalWakefulnessLocked() != WAKEFULNESS_DREAMING
|| !mDreamsSupportedConfig
|| !mDreamsEnabledSetting
@@ -4408,6 +4457,8 @@ public final class PowerManagerService extends SystemService
+ mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig);
pw.println(" mTheaterModeEnabled="
+ mTheaterModeEnabled);
+ pw.println(" mKeepDreamingWhenUndockingConfig="
+ + mKeepDreamingWhenUndockingConfig);
pw.println(" mSuspendWhenScreenOffDueToProximityConfig="
+ mSuspendWhenScreenOffDueToProximityConfig);
pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig);
@@ -5013,6 +5064,16 @@ public final class PowerManagerService extends SystemService
}
};
+ private final AmbientDisplaySuppressionChangedCallback mAmbientSuppressionChangedCallback =
+ new AmbientDisplaySuppressionChangedCallback() {
+ @Override
+ public void onSuppressionChanged(boolean isSuppressed) {
+ synchronized (mLock) {
+ onDreamSuppressionChangedLocked(isSuppressed);
+ }
+ }
+ };
+
/**
* Callback for asynchronous operations performed by the power manager.
*/
@@ -5701,10 +5762,7 @@ public final class PowerManagerService extends SystemService
final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- dreamPowerGroupLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
- eventTime, uid);
- }
+ napInternal(eventTime, uid, /* allowWake= */ false);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6632,6 +6690,16 @@ public final class PowerManagerService extends SystemService
public boolean interceptPowerKeyDown(KeyEvent event) {
return interceptPowerKeyDownInternal(event);
}
+
+ @Override
+ public void nap(long eventTime, boolean allowWake) {
+ napInternal(eventTime, Process.SYSTEM_UID, allowWake);
+ }
+
+ @Override
+ public boolean isAmbientDisplaySuppressed() {
+ return mAmbientDisplaySuppressionController.isSuppressed();
+ }
}
/**
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 653b51a95993..3df8f584fef1 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -2253,6 +2253,25 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ boolean proto = false;
+ for (int i = 0; i < args.length; i++) {
+ if ("--proto".equals(args[i])) {
+ proto = true;
+ }
+ }
+ if (proto) {
+ if (mBar == null) return;
+ try (TransferPipe tp = new TransferPipe()) {
+ // Sending the command to the remote, which needs to execute async to avoid blocking
+ // See Binder#dumpAsync() for inspiration
+ mBar.dumpProto(args, tp.getWriteFd());
+ // Times out after 5s
+ tp.go(fd);
+ } catch (Throwable t) {
+ Slog.e(TAG, "Error sending command to IStatusBar", t);
+ }
+ return;
+ }
synchronized (mLock) {
for (int i = 0; i < mDisplayUiState.size(); i++) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 0799b955b6f1..9c455dbb5320 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -65,6 +65,9 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
public final DeviceVibrationEffectAdapter deviceEffectAdapter;
public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
+ // Not guarded by lock because they're not modified by this conductor, it's used here only to
+ // check immutable attributes. The status and other mutable states are changed by the service or
+ // by the vibrator steps.
private final Vibration mVibration;
private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
@@ -405,6 +408,16 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
}
}
+ /** Returns true if a cancellation signal was sent via {@link #notifyCancelled}. */
+ public boolean wasNotifiedToCancel() {
+ if (Build.IS_DEBUGGABLE) {
+ expectIsVibrationThread(false);
+ }
+ synchronized (mLock) {
+ return mSignalCancel != null;
+ }
+ }
+
@GuardedBy("mLock")
private boolean hasPendingNotifySignalLocked() {
if (Build.IS_DEBUGGABLE) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 97275583ac36..14693599c3ec 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -852,8 +852,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
Vibration currentVibration = mCurrentVibration.getVibration();
- if (currentVibration.hasEnded()) {
- // Current vibration is finishing up, it should not block incoming vibrations.
+ if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) {
+ // Current vibration has ended or is cancelling, should not block incoming vibrations.
return null;
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index f25929c36060..189b86fd0ff1 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1166,7 +1166,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
try {
connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
wpdData.mWidth, wpdData.mHeight,
- wpdData.mPadding, mDisplayId);
+ wpdData.mPadding, mDisplayId, FLAG_SYSTEM | FLAG_LOCK);
} catch (RemoteException e) {
Slog.w(TAG, "Failed attaching wallpaper on display", e);
if (wallpaper != null && !wallpaper.wallpaperUpdating
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 30454d45574e..9f502ab6ca10 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -389,9 +389,9 @@ public class ActivityStartController {
SafeActivityOptions bottomOptions = null;
if (options != null) {
// To ensure the first N-1 activities (N == total # of activities) are also launched
- // into the correct display, use a copy of the passed-in options (keeping only
- // display-related info) for these activities.
- bottomOptions = options.selectiveCloneDisplayOptions();
+ // into the correct display and root task, use a copy of the passed-in options (keeping
+ // only display-related and launch-root-task information) for these activities.
+ bottomOptions = options.selectiveCloneLaunchOptions();
}
try {
intents = ArrayUtils.filterNotNull(intents, Intent[]::new);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e3916cbc30bd..fe691c61a96b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2593,6 +2593,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// activity lifecycle transaction to make sure the override pending app
// transition will be applied immediately.
targetActivity.applyOptionsAnimation();
+ if (activityOptions != null && activityOptions.getLaunchCookie() != null) {
+ targetActivity.mLaunchCookie = activityOptions.getLaunchCookie();
+ }
} finally {
mActivityMetricsLogger.notifyActivityLaunched(launchingState,
START_TASK_TO_FRONT, false /* newActivityCreated */,
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index e69a732b3fde..6d1b5fa6034d 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -117,13 +117,13 @@ public class SafeActivityOptions {
/**
* To ensure that two activities, one using this object, and the other using the
- * SafeActivityOptions returned from this function, are launched into the same display through
- * ActivityStartController#startActivities, all display-related information, i.e.
- * displayAreaToken, launchDisplayId and callerDisplayId, are cloned.
+ * SafeActivityOptions returned from this function, are launched into the same display/root task
+ * through ActivityStartController#startActivities, all display-related information, i.e.
+ * displayAreaToken, launchDisplayId, callerDisplayId and the launch root task are cloned.
*/
- @Nullable SafeActivityOptions selectiveCloneDisplayOptions() {
- final ActivityOptions options = cloneLaunchingDisplayOptions(mOriginalOptions);
- final ActivityOptions callerOptions = cloneLaunchingDisplayOptions(mCallerOptions);
+ @Nullable SafeActivityOptions selectiveCloneLaunchOptions() {
+ final ActivityOptions options = cloneLaunchingOptions(mOriginalOptions);
+ final ActivityOptions callerOptions = cloneLaunchingOptions(mCallerOptions);
if (options == null && callerOptions == null) {
return null;
}
@@ -136,11 +136,12 @@ public class SafeActivityOptions {
return safeOptions;
}
- private ActivityOptions cloneLaunchingDisplayOptions(ActivityOptions options) {
+ private ActivityOptions cloneLaunchingOptions(ActivityOptions options) {
return options == null ? null : ActivityOptions.makeBasic()
.setLaunchTaskDisplayArea(options.getLaunchTaskDisplayArea())
.setLaunchDisplayId(options.getLaunchDisplayId())
- .setCallerDisplayId((options.getCallerDisplayId()));
+ .setCallerDisplayId(options.getCallerDisplayId())
+ .setLaunchRootTask(options.getLaunchRootTask());
}
/**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 731754e1f0cb..eba49bbc7301 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -21,7 +21,6 @@ import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED;
import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -5855,12 +5854,10 @@ class Task extends TaskFragment {
return false;
}
- // Existing Tasks can be reused if a new root task will be created anyway, or for the
- // Dream - because there can only ever be one DreamActivity.
+ // Existing Tasks can be reused if a new root task will be created anyway.
final int windowingMode = getWindowingMode();
final int activityType = getActivityType();
- return DisplayContent.alwaysCreateRootTask(windowingMode, activityType)
- || activityType == ACTIVITY_TYPE_DREAM;
+ return DisplayContent.alwaysCreateRootTask(windowingMode, activityType);
}
void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) {
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 4a51b831da57..f53a1cfcfb3c 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -383,6 +383,7 @@
</xs:complexType>
<xs:complexType name="autoBrightness">
+ <xs:attribute name="enabled" type="xs:boolean" use="optional" default="true"/>
<xs:sequence>
<!-- Sets the debounce for autoBrightness brightening in millis-->
<xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 748ef4be9cc7..d89bd7cc9aa2 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -6,9 +6,11 @@ package com.android.server.display.config {
method public final java.math.BigInteger getBrighteningLightDebounceMillis();
method public final java.math.BigInteger getDarkeningLightDebounceMillis();
method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping();
+ method public boolean getEnabled();
method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping);
+ method public void setEnabled(boolean);
}
public class BrightnessThresholds {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2ee4af56d320..46141940a907 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1823,7 +1823,8 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartStatusBarManagerService");
try {
statusBar = new StatusBarManagerService(context);
- ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);
+ ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false,
+ DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
} catch (Throwable e) {
reportWtf("starting StatusBarManagerService", e);
}
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
index 45e0d09a1753..4ef6875dd310 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
@@ -628,6 +628,7 @@ class SystemStubMultiUserDisableUninstallTest : BaseHostJUnit4Test() {
CodePath.SAME, CodePath.DIFFERENT ->
throw AssertionError("secondDataPath cannot be a data path")
CodePath.SYSTEM -> assertThat(codePaths[1]).isEqualTo(stubFile.parent.toString())
+ else -> {}
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index b9d6b2ccd306..994df2288a5c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -392,7 +392,7 @@ class SuspendPackageHelperTest : PackageHelperTestBase() {
whenever(rule.mocks().appsFilter.getVisibilityAllowList(
any(PackageDataSnapshot::class.java),
argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java),
- any() as ArrayMap<String, out PackageStateInternal>
+ any<ArrayMap<String, out PackageStateInternal>>()
))
.thenReturn(list)
}
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 a5c181d53286..606f4864643c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -41,6 +41,7 @@ import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -73,6 +74,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.time.Clock;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -128,6 +130,8 @@ public class FingerprintAuthenticationClientTest {
private ICancellationSignal mCancellationSignal;
@Mock
private Probe mLuxProbe;
+ @Mock
+ private Clock mClock;
@Captor
private ArgumentCaptor<OperationContext> mOperationContextCaptor;
@Captor
@@ -447,6 +451,52 @@ public class FingerprintAuthenticationClientTest {
}
@Test
+ public void sideFingerprintSkipsWindowIfVendorMessageMatch() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+ final int vendorAcquireMessage = 1234;
+
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_sidefpsSkipWaitForPowerAcquireMessage,
+ FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage,
+ vendorAcquireMessage);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ mLooper.dispatchAll();
+ client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, vendorAcquireMessage);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onClientFinished(any(), eq(true));
+ }
+
+ @Test
+ public void sideFingerprintDoesNotSkipWindowOnVendorErrorMismatch() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+ final int vendorAcquireMessage = 1234;
+
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_sidefpsSkipWaitForPowerAcquireMessage,
+ FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage,
+ vendorAcquireMessage);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ mLooper.dispatchAll();
+ client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 1);
+ mLooper.dispatchAll();
+
+ verify(mCallback, never()).onClientFinished(any(), anyBoolean());
+ }
+
+ @Test
public void sideFingerprintSendsAuthIfFingerUp() throws Exception {
when(mSensorProps.isAnySidefpsType()).thenReturn(true);
@@ -493,6 +543,79 @@ public class FingerprintAuthenticationClientTest {
verify(mCallback).onClientFinished(any(), eq(true));
}
+ @Test
+ public void sideFingerprintPowerWindowStartsOnAcquireStart() throws Exception {
+ final int powerWindow = 500;
+ final long authStart = 300;
+
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_sidefpsBpPowerPressWindow, powerWindow);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+
+ // Acquire start occurs at time = 0ms
+ when(mClock.millis()).thenReturn(0L);
+ client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
+
+ // Auth occurs at time = 300
+ when(mClock.millis()).thenReturn(authStart);
+ // At this point the delay should be 500 - (300 - 0) == 200 milliseconds.
+ client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ mLooper.dispatchAll();
+ verify(mCallback, never()).onClientFinished(any(), anyBoolean());
+
+ // After waiting 200 milliseconds, auth should succeed.
+ mLooper.moveTimeForward(powerWindow - authStart);
+ mLooper.dispatchAll();
+ verify(mCallback).onClientFinished(any(), eq(true));
+ }
+
+ @Test
+ public void sideFingerprintPowerWindowStartsOnLastAcquireStart() throws Exception {
+ final int powerWindow = 500;
+
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.integer.config_sidefpsBpPowerPressWindow, powerWindow);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ // Acquire start occurs at time = 0ms
+ when(mClock.millis()).thenReturn(0L);
+ client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
+
+ // Auth reject occurs at time = 300ms
+ when(mClock.millis()).thenReturn(300L);
+ client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+ false /* authenticated */, new ArrayList<>());
+ mLooper.dispatchAll();
+
+ mLooper.moveTimeForward(300);
+ mLooper.dispatchAll();
+ verify(mCallback, never()).onClientFinished(any(), anyBoolean());
+
+ when(mClock.millis()).thenReturn(1300L);
+ client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
+
+ // If code is correct, the new acquired start timestamp should be used
+ // and the code should only have to wait 500 - (1500-1300)ms.
+ when(mClock.millis()).thenReturn(1500L);
+ client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ mLooper.dispatchAll();
+
+ mLooper.moveTimeForward(299);
+ mLooper.dispatchAll();
+ verify(mCallback, never()).onClientFinished(any(), anyBoolean());
+
+ mLooper.moveTimeForward(1);
+ mLooper.dispatchAll();
+ verify(mCallback).onClientFinished(any(), eq(true));
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
}
@@ -520,7 +643,7 @@ public class FingerprintAuthenticationClientTest {
null /* taskStackListener */, mLockoutCache,
mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
mSensorProps,
- new Handler(mLooper.getLooper())) {
+ new Handler(mLooper.getLooper()), mClock) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 356600d84099..06422281ab25 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -885,6 +886,29 @@ public class BrightnessTrackerTest {
assertNull(mInjector.mLightSensor);
}
+ @Test
+ public void testOnlyOneReceiverRegistered() {
+ assertNull(mInjector.mLightSensor);
+ assertNull(mInjector.mSensorListener);
+ startTracker(mTracker, 0.3f, false);
+
+ assertNotNull(mInjector.mLightSensor);
+ assertNotNull(mInjector.mSensorListener);
+ Sensor registeredLightSensor = mInjector.mLightSensor;
+ SensorEventListener registeredSensorListener = mInjector.mSensorListener;
+
+ mTracker.start(0.3f);
+ assertSame(registeredLightSensor, mInjector.mLightSensor);
+ assertSame(registeredSensorListener, mInjector.mSensorListener);
+
+ mTracker.stop();
+ assertNull(mInjector.mLightSensor);
+ assertNull(mInjector.mSensorListener);
+
+ // mInjector asserts that we aren't removing a null receiver
+ mTracker.stop();
+ }
+
private InputStream getInputStream(String data) {
return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index a719f526a1d8..30024fb5c221 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -49,6 +49,13 @@ import java.nio.file.Path;
@Presubmit
@RunWith(AndroidJUnit4.class)
public final class DisplayDeviceConfigTest {
+ private static final int DEFAULT_PEAK_REFRESH_RATE = 75;
+ private static final int DEFAULT_REFRESH_RATE = 120;
+ private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{10, 30};
+ private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{1, 21};
+ private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160};
+ private static final int[] HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{30000};
+
private DisplayDeviceConfig mDisplayDeviceConfig;
private static final float ZERO_DELTA = 0.0f;
private static final float SMALL_DELTA = 0.0001f;
@@ -204,6 +211,16 @@ public final class DisplayDeviceConfigTest {
assertArrayEquals(new float[]{29, 30, 31},
mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+ assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
+ assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(),
+ LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
+ assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(),
+ LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
+ assertArrayEquals(mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(),
+ HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
+ assertArrayEquals(mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(),
+ HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
}
@@ -465,6 +482,21 @@ public final class DisplayDeviceConfigTest {
when(mResources.getIntArray(R.array.config_screenDarkeningThresholds))
.thenReturn(new int[]{370, 380, 390});
+ // Configs related to refresh rates and blocking zones
+ when(mResources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+ .thenReturn(DEFAULT_PEAK_REFRESH_RATE);
+ when(mResources.getInteger(com.android.internal.R.integer.config_defaultRefreshRate))
+ .thenReturn(DEFAULT_REFRESH_RATE);
+ when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+ .thenReturn(LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
+ when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+ .thenReturn(LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
+ when(mResources.getIntArray(
+ R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
+ when(mResources.getIntArray(
+ R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
}
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 968e1d8c546b..18dd264558ac 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -1853,6 +1853,20 @@ public class DisplayModeDirectorTest {
assertNull(vote);
}
+ @Test
+ public void testNotifyDefaultDisplayDeviceUpdated() {
+ DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
+ when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{});
+ when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{});
+ when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{});
+ when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{});
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+ director.defaultDisplayDeviceUpdated(displayDeviceConfig);
+ verify(displayDeviceConfig).getDefaultRefreshRate();
+ verify(displayDeviceConfig).getDefaultPeakRefreshRate();
+ }
+
private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 9fe8609c85a1..3b0a22f80c30 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -275,6 +275,75 @@ public class PersistentDataStoreTest {
assertNull(mDataStore.getBrightnessConfiguration(userSerial));
}
+ @Test
+ public void testStoreAndRestoreResolution() {
+ final String uniqueDisplayId = "test:123";
+ DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+ @Override
+ public boolean hasStableUniqueId() {
+ return true;
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ return null;
+ }
+ };
+ int width = 35;
+ int height = 45;
+ mDataStore.loadIfNeeded();
+ mDataStore.setUserPreferredResolution(testDisplayDevice, width, height);
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mInjector.setWriteStream(baos);
+ mDataStore.saveIfNeeded();
+ mTestLooper.dispatchAll();
+ assertTrue(mInjector.wasWriteSuccessful());
+ TestInjector newInjector = new TestInjector();
+ PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ newInjector.setReadStream(bais);
+ newDataStore.loadIfNeeded();
+ assertNotNull(newDataStore.getUserPreferredResolution(testDisplayDevice));
+ assertEquals(35, newDataStore.getUserPreferredResolution(testDisplayDevice).x);
+ assertEquals(35, mDataStore.getUserPreferredResolution(testDisplayDevice).x);
+ assertEquals(45, newDataStore.getUserPreferredResolution(testDisplayDevice).y);
+ assertEquals(45, mDataStore.getUserPreferredResolution(testDisplayDevice).y);
+ }
+
+ @Test
+ public void testStoreAndRestoreRefreshRate() {
+ final String uniqueDisplayId = "test:123";
+ DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+ @Override
+ public boolean hasStableUniqueId() {
+ return true;
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ return null;
+ }
+ };
+ float refreshRate = 85.3f;
+ mDataStore.loadIfNeeded();
+ mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate);
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mInjector.setWriteStream(baos);
+ mDataStore.saveIfNeeded();
+ mTestLooper.dispatchAll();
+ assertTrue(mInjector.wasWriteSuccessful());
+ TestInjector newInjector = new TestInjector();
+ PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ newInjector.setReadStream(bais);
+ newDataStore.loadIfNeeded();
+ assertNotNull(newDataStore.getUserPreferredRefreshRate(testDisplayDevice));
+ assertEquals(85.3f, mDataStore.getUserPreferredRefreshRate(testDisplayDevice), 01.f);
+ assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
+ }
+
public class TestInjector extends PersistentDataStore.Injector {
private InputStream mReadStream;
private OutputStream mWriteStream;
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
new file mode 100644
index 000000000000..303a370b0ba9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.service.dreams.IDreamService;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DreamControllerTest {
+ @Mock
+ private DreamController.Listener mListener;
+ @Mock
+ private Context mContext;
+ @Mock
+ private IBinder mIBinder;
+ @Mock
+ private IDreamService mIDreamService;
+
+ @Captor
+ private ArgumentCaptor<ServiceConnection> mServiceConnectionACaptor;
+ @Captor
+ private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor;
+
+ private final TestLooper mLooper = new TestLooper();
+ private final Handler mHandler = new Handler(mLooper.getLooper());
+
+ private DreamController mDreamController;
+
+ private Binder mToken;
+ private ComponentName mDreamName;
+ private ComponentName mOverlayName;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mIDreamService.asBinder()).thenReturn(mIBinder);
+ when(mIBinder.queryLocalInterface(anyString())).thenReturn(mIDreamService);
+ when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+
+ mToken = new Binder();
+ mDreamName = ComponentName.unflattenFromString("dream");
+ mOverlayName = ComponentName.unflattenFromString("dream_overlay");
+ mDreamController = new DreamController(mContext, mHandler, mListener);
+ }
+
+ @Test
+ public void startDream_attachOnServiceConnected() throws RemoteException {
+ // Call dream controller to start dreaming.
+ mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+ 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+
+ // Mock service connected.
+ final ServiceConnection serviceConnection = captureServiceConnection();
+ serviceConnection.onServiceConnected(mDreamName, mIBinder);
+ mLooper.dispatchAll();
+
+ // Verify that dream service is called to attach.
+ verify(mIDreamService).attach(eq(mToken), eq(false) /*doze*/, any());
+ }
+
+ @Test
+ public void startDream_startASecondDream_detachOldDreamOnceNewDreamIsStarted()
+ throws RemoteException {
+ // Start first dream.
+ mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+ 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+ captureServiceConnection().onServiceConnected(mDreamName, mIBinder);
+ mLooper.dispatchAll();
+ clearInvocations(mContext);
+
+ // Set up second dream.
+ final Binder newToken = new Binder();
+ final ComponentName newDreamName = ComponentName.unflattenFromString("new_dream");
+ final ComponentName newOverlayName = ComponentName.unflattenFromString("new_dream_overlay");
+ final IDreamService newDreamService = mock(IDreamService.class);
+ final IBinder newBinder = mock(IBinder.class);
+ when(newDreamService.asBinder()).thenReturn(newBinder);
+ when(newBinder.queryLocalInterface(anyString())).thenReturn(newDreamService);
+
+ // Start second dream.
+ mDreamController.startDream(newToken, newDreamName, false /*isPreview*/, false /*doze*/,
+ 0 /*userId*/, null /*wakeLock*/, newOverlayName, "test" /*reason*/);
+ captureServiceConnection().onServiceConnected(newDreamName, newBinder);
+ mLooper.dispatchAll();
+
+ // Mock second dream started.
+ verify(newDreamService).attach(eq(newToken), eq(false) /*doze*/,
+ mRemoteCallbackCaptor.capture());
+ mRemoteCallbackCaptor.getValue().sendResult(null /*data*/);
+ mLooper.dispatchAll();
+
+ // Verify that the first dream is called to detach.
+ verify(mIDreamService).detach();
+ }
+
+ @Test
+ public void stopDream_detachFromService() throws RemoteException {
+ // Start dream.
+ mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
+ 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+ captureServiceConnection().onServiceConnected(mDreamName, mIBinder);
+ mLooper.dispatchAll();
+
+ // Stop dream.
+ mDreamController.stopDream(true /*immediate*/, "test stop dream" /*reason*/);
+
+ // Verify that dream service is called to detach.
+ verify(mIDreamService).detach();
+ }
+
+ private ServiceConnection captureServiceConnection() {
+ verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(),
+ any());
+ return mServiceConnectionACaptor.getValue();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
index d8c9c3433313..e3ca1707ae0c 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
@@ -116,7 +116,7 @@ public class PowerGroupTest {
@Test
public void testDreamPowerGroup() {
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
- mPowerGroup.dreamLocked(TIMESTAMP1, UID);
+ mPowerGroup.dreamLocked(TIMESTAMP1, UID, /* allowWake= */ false);
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
assertThat(mPowerGroup.isSandmanSummonedLocked()).isTrue();
verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
@@ -172,7 +172,7 @@ public class PowerGroupTest {
eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(),
/* details= */ isNull());
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
- assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID)).isFalse();
+ assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID, /* allowWake= */ false)).isFalse();
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
verify(mWakefulnessCallbackMock, never()).onWakefulnessChangedLocked(
eq(GROUP_ID), /* wakefulness= */ eq(WAKEFULNESS_DREAMING), eq(TIMESTAMP2),
@@ -181,6 +181,22 @@ public class PowerGroupTest {
}
@Test
+ public void testDreamPowerGroupWhenNotAwakeShouldWake() {
+ mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
+ verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
+ eq(WAKEFULNESS_DOZING), eq(TIMESTAMP1), eq(GO_TO_SLEEP_REASON_TIMEOUT),
+ eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ isNull(),
+ /* details= */ isNull());
+ assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+ assertThat(mPowerGroup.dreamLocked(TIMESTAMP2, UID, /* allowWake= */ true)).isTrue();
+ assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(
+ eq(GROUP_ID), /* wakefulness= */ eq(WAKEFULNESS_DREAMING), eq(TIMESTAMP2),
+ /* reason= */ anyInt(), eq(UID), /* opUid= */ anyInt(), /* opPackageName= */ any(),
+ /* details= */ any());
+ }
+
+ @Test
public void testLastWakeAndSleepTimeIsUpdated() {
assertThat(mPowerGroup.getLastWakeTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
assertThat(mPowerGroup.getLastSleepTimeLocked()).isEqualTo(TIMESTAMP_CREATE);
@@ -514,7 +530,7 @@ public class PowerGroupTest {
.setBatterySaverEnabled(batterySaverEnabled)
.setBrightnessFactor(brightnessFactor)
.build();
- mPowerGroup.dreamLocked(TIMESTAMP1, UID);
+ mPowerGroup.dreamLocked(TIMESTAMP1, UID, /* allowWake= */ false);
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_SCREEN_BRIGHT);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 2a6e6d876599..f5ed41a7fa8b 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -393,6 +393,12 @@ public class PowerManagerServiceTest {
.thenReturn(minimumScreenOffTimeoutConfigMillis);
}
+ private void setDreamsDisabledByAmbientModeSuppressionConfig(boolean disable) {
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig))
+ .thenReturn(disable);
+ }
+
private void advanceTime(long timeMs) {
mClock.fastForward(timeMs);
mTestLooper.dispatchAll();
@@ -612,6 +618,31 @@ public class PowerManagerServiceTest {
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
}
+ /**
+ * Tests that dreaming continues when undocking and configured to do so.
+ */
+ @Test
+ public void testWakefulnessDream_shouldKeepDreamingWhenUndocked() {
+ createService();
+ startSystem();
+
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_keepDreamingWhenUndocking))
+ .thenReturn(true);
+ mService.readConfigurationLocked();
+
+ when(mBatteryManagerInternalMock.getPlugType())
+ .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+ setPluggedIn(true);
+
+ forceAwake(); // Needs to be awake first before it can dream.
+ forceDream();
+ when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+ setPluggedIn(false);
+
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ }
+
@Test
public void testWakefulnessDoze_goToSleep() {
createService();
@@ -765,6 +796,91 @@ public class PowerManagerServiceTest {
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+ setDreamsDisabledByAmbientModeSuppressionConfig(true);
+ setMinimumScreenOffTimeoutConfig(10000);
+ createService();
+ startSystem();
+
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ setPluggedIn(true);
+ // Allow asynchronous sandman calls to execute.
+ advanceTime(10000);
+
+ forceDream();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+ advanceTime(50);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testAmbientSuppressionDisabled_shouldNotWakeDevice() {
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+ setDreamsDisabledByAmbientModeSuppressionConfig(false);
+ setMinimumScreenOffTimeoutConfig(10000);
+ createService();
+ startSystem();
+
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ setPluggedIn(true);
+ // Allow asynchronous sandman calls to execute.
+ advanceTime(10000);
+
+ forceDream();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+ advanceTime(50);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ }
+
+ @Test
+ public void testAmbientSuppression_doesNotAffectDreamForcing() {
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+ setDreamsDisabledByAmbientModeSuppressionConfig(true);
+ setMinimumScreenOffTimeoutConfig(10000);
+ createService();
+ startSystem();
+
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ mService.getBinderServiceInstance().suppressAmbientDisplay("test", true);
+ setPluggedIn(true);
+ // Allow asynchronous sandman calls to execute.
+ advanceTime(10000);
+
+ // Verify that forcing dream still works even though ambient display is suppressed
+ forceDream();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ }
+
@Test
public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() {
final String suspendBlockerName = "PowerManagerService.Display";
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 235849c1cd8b..c484f457faea 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -53,7 +53,8 @@ final class FakeVibratorControllerProvider {
private boolean mIsAvailable = true;
private boolean mIsInfoLoadSuccessful = true;
- private long mLatency;
+ private long mOnLatency;
+ private long mOffLatency;
private int mOffCount;
private int mCapabilities;
@@ -97,7 +98,7 @@ final class FakeVibratorControllerProvider {
public long on(long milliseconds, long vibrationId) {
recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
/* frequencyHz= */ 0, (int) milliseconds));
- applyLatency();
+ applyLatency(mOnLatency);
scheduleListener(milliseconds, vibrationId);
return milliseconds;
}
@@ -105,12 +106,13 @@ final class FakeVibratorControllerProvider {
@Override
public void off() {
mOffCount++;
+ applyLatency(mOffLatency);
}
@Override
public void setAmplitude(float amplitude) {
mAmplitudes.add(amplitude);
- applyLatency();
+ applyLatency(mOnLatency);
}
@Override
@@ -121,7 +123,7 @@ final class FakeVibratorControllerProvider {
}
recordEffectSegment(vibrationId,
new PrebakedSegment((int) effect, false, (int) strength));
- applyLatency();
+ applyLatency(mOnLatency);
scheduleListener(EFFECT_DURATION, vibrationId);
return EFFECT_DURATION;
}
@@ -141,7 +143,7 @@ final class FakeVibratorControllerProvider {
duration += EFFECT_DURATION + primitive.getDelay();
recordEffectSegment(vibrationId, primitive);
}
- applyLatency();
+ applyLatency(mOnLatency);
scheduleListener(duration, vibrationId);
return duration;
}
@@ -154,7 +156,7 @@ final class FakeVibratorControllerProvider {
recordEffectSegment(vibrationId, primitive);
}
recordBraking(vibrationId, braking);
- applyLatency();
+ applyLatency(mOnLatency);
scheduleListener(duration, vibrationId);
return duration;
}
@@ -193,10 +195,10 @@ final class FakeVibratorControllerProvider {
return mIsInfoLoadSuccessful;
}
- private void applyLatency() {
+ private void applyLatency(long latencyMillis) {
try {
- if (mLatency > 0) {
- Thread.sleep(mLatency);
+ if (latencyMillis > 0) {
+ Thread.sleep(latencyMillis);
}
} catch (InterruptedException e) {
}
@@ -240,10 +242,15 @@ final class FakeVibratorControllerProvider {
/**
* Sets the latency this controller should fake for turning the vibrator hardware on or setting
- * it's vibration amplitude.
+ * the vibration amplitude.
*/
- public void setLatency(long millis) {
- mLatency = millis;
+ public void setOnLatency(long millis) {
+ mOnLatency = millis;
+ }
+
+ /** Sets the latency this controller should fake for turning the vibrator off. */
+ public void setOffLatency(long millis) {
+ mOffLatency = millis;
}
/** Set the capabilities of the fake vibrator hardware. */
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 0551bfc70bda..42a2c10a24d6 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -1118,7 +1118,7 @@ public class VibrationThreadTest {
// 25% of the first waveform step will be spent on the native on() call.
// 25% of each waveform step will be spent on the native setAmplitude() call..
- mVibratorProviders.get(VIBRATOR_ID).setLatency(stepDuration / 4);
+ mVibratorProviders.get(VIBRATOR_ID).setOnLatency(stepDuration / 4);
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
int stepCount = totalDuration / stepDuration;
@@ -1149,7 +1149,7 @@ public class VibrationThreadTest {
fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
long latency = 5_000; // 5s
- fakeVibrator.setLatency(latency);
+ fakeVibrator.setOnLatency(latency);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
@@ -1163,8 +1163,7 @@ public class VibrationThreadTest {
// fail at waitForCompletion(cancellingThread).
Thread cancellingThread = new Thread(
() -> conductor.notifyCancelled(
- new Vibration.EndInfo(
- Vibration.Status.CANCELLED_BY_USER),
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
/* immediate= */ false));
cancellingThread.start();
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index fe0a79c2d944..039e159a347b 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -817,6 +817,39 @@ public class VibratorManagerServiceTest {
verify(mBatteryStatsMock).noteVibratorOn(UID, 5000);
// The second vibration shouldn't have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+ // No segment played is the prebaked CLICK from the second vibration.
+ assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+ .anyMatch(PrebakedSegment.class::isInstance));
+ // Clean up repeating effect.
+ service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+ }
+
+ @Test
+ public void vibrate_withOngoingRepeatingVibrationBeingCancelled_playsAfterPreviousIsCancelled()
+ throws Exception {
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setOffLatency(50); // Add latency so cancellation is slow.
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
+ new long[]{10, 10_000}, new int[]{255, 0}, 1);
+ vibrate(service, repeatingEffect, ALARM_ATTRS);
+
+ // VibrationThread will start this vibration async, wait until the off waveform step.
+ assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+
+ // Cancel vibration right before requesting a new one.
+ // This should trigger slow IVibrator.off before setting the vibration status to cancelled.
+ service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
+ vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ ALARM_ATTRS);
+
+ // Check that second vibration was played.
+ assertTrue(fakeVibrator.getAllEffectSegments().stream()
+ .anyMatch(PrebakedSegment.class::isInstance));
}
@Test
@@ -867,6 +900,11 @@ public class VibratorManagerServiceTest {
// The second vibration shouldn't have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+ // The second vibration shouldn't have played any prebaked segment.
+ assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+ .anyMatch(PrebakedSegment.class::isInstance));
+ // Clean up long effect.
+ service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 91c2fe0eb262..8e81e2d8997c 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -1371,6 +1371,39 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
+ @Test
+ public void dreamWhenDocked_ambientModeSuppressed_suppressionEnabled() {
+ mUiManagerService.setStartDreamImmediatelyOnDock(true);
+ mUiManagerService.setDreamsDisabledByAmbientModeSuppression(true);
+
+ when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(true);
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mInjector, never()).startDreamWhenDockedIfAppropriate(mContext);
+ }
+
+ @Test
+ public void dreamWhenDocked_ambientModeSuppressed_suppressionDisabled() {
+ mUiManagerService.setStartDreamImmediatelyOnDock(true);
+ mUiManagerService.setDreamsDisabledByAmbientModeSuppression(false);
+
+ when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(true);
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
+ }
+
+ @Test
+ public void dreamWhenDocked_ambientModeNotSuppressed_suppressionEnabled() {
+ mUiManagerService.setStartDreamImmediatelyOnDock(true);
+ mUiManagerService.setDreamsDisabledByAmbientModeSuppression(true);
+
+ when(mLocalPowerManager.isAmbientDisplaySuppressed()).thenReturn(false);
+ triggerDockIntent();
+ verifyAndSendResultBroadcast();
+ verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
+ }
+
private void triggerDockIntent() {
final Intent dockedIntent =
new Intent(Intent.ACTION_DOCK_EVENT)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index c5131c8f8c1d..101a5cb49f9b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -30,9 +31,11 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.intThat;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -49,6 +52,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.pm.VersionedPackage;
+import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationListenerFilter;
@@ -69,6 +73,7 @@ import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.internal.util.reflection.FieldSetter;
@@ -77,6 +82,7 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
import java.util.List;
public class NotificationListenersTest extends UiServiceTestCase {
@@ -85,6 +91,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
private PackageManager mPm;
@Mock
private IPackageManager miPm;
+ @Mock
+ private Resources mResources;
@Mock
NotificationManagerService mNm;
@@ -96,7 +104,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
private ComponentName mCn1 = new ComponentName("pkg", "pkg.cmp");
private ComponentName mCn2 = new ComponentName("pkg2", "pkg2.cmp2");
-
+ private ComponentName mUninstalledComponent = new ComponentName("pkg3",
+ "pkg3.NotificationListenerService");
@Before
public void setUp() throws Exception {
@@ -111,7 +120,7 @@ public class NotificationListenersTest extends UiServiceTestCase {
@Test
public void testReadExtraTag() throws Exception {
- String xml = "<" + TAG_REQUESTED_LISTENERS+ ">"
+ String xml = "<" + TAG_REQUESTED_LISTENERS + ">"
+ "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">"
+ "<allowed types=\"7\" />"
+ "</listener>"
@@ -131,11 +140,55 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
+ public void loadDefaultsFromConfig_forHeadlessSystemUser_loadUninstalled() throws Exception {
+ // setup with headless system user mode
+ mListeners = spy(mNm.new NotificationListeners(
+ mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm,
+ /* isHeadlessSystemUserMode= */ true));
+ mockDefaultListenerConfigForUninstalledComponent(mUninstalledComponent);
+
+ mListeners.loadDefaultsFromConfig();
+
+ assertThat(mListeners.getDefaultComponents()).contains(mUninstalledComponent);
+ }
+
+ @Test
+ public void loadDefaultsFromConfig_forNonHeadlessSystemUser_ignoreUninstalled()
+ throws Exception {
+ // setup without headless system user mode
+ mListeners = spy(mNm.new NotificationListeners(
+ mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm,
+ /* isHeadlessSystemUserMode= */ false));
+ mockDefaultListenerConfigForUninstalledComponent(mUninstalledComponent);
+
+ mListeners.loadDefaultsFromConfig();
+
+ assertThat(mListeners.getDefaultComponents()).doesNotContain(mUninstalledComponent);
+ }
+
+ private void mockDefaultListenerConfigForUninstalledComponent(ComponentName componentName) {
+ ArraySet<ComponentName> components = new ArraySet<>(Arrays.asList(componentName));
+ when(mResources
+ .getString(
+ com.android.internal.R.string.config_defaultListenerAccessPackages))
+ .thenReturn(componentName.getPackageName());
+ when(mContext.getResources()).thenReturn(mResources);
+ doReturn(components).when(mListeners).queryPackageForServices(
+ eq(componentName.getPackageName()),
+ intThat(hasIntBitFlag(MATCH_ANY_USER)),
+ anyInt());
+ }
+
+ public static ArgumentMatcher<Integer> hasIntBitFlag(int flag) {
+ return arg -> arg != null && ((arg & flag) == flag);
+ }
+
+ @Test
public void testWriteExtraTag() throws Exception {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
VersionedPackage a1 = new VersionedPackage("pkg1", 243);
NotificationListenerFilter nlf2 =
- new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+ new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[]{a1}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java
deleted file mode 100644
index d7650420788c..000000000000
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java
+++ /dev/null
@@ -1,551 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.notification;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Person;
-import android.app.RemoteInput;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Typeface;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.style.StyleSpan;
-import android.util.Pair;
-import android.widget.RemoteViews;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.UiServiceTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class NotificationTest extends UiServiceTestCase {
-
- @Mock
- ActivityManager mAm;
-
- @Mock
- Resources mResources;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void testDoesNotStripsExtenders() {
- Notification.Builder nb = new Notification.Builder(mContext, "channel");
- nb.extend(new Notification.CarExtender().setColor(Color.RED));
- nb.extend(new Notification.TvExtender().setChannelId("different channel"));
- nb.extend(new Notification.WearableExtender().setDismissalId("dismiss"));
- Notification before = nb.build();
- Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before);
-
- assertTrue(before == after);
-
- assertEquals("different channel", new Notification.TvExtender(before).getChannelId());
- assertEquals(Color.RED, new Notification.CarExtender(before).getColor());
- assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId());
- }
-
- @Test
- public void testStyleChangeVisiblyDifferent_noStyles() {
- Notification.Builder n1 = new Notification.Builder(mContext, "test");
- Notification.Builder n2 = new Notification.Builder(mContext, "test");
-
- assertFalse(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testStyleChangeVisiblyDifferent_noStyleToStyle() {
- Notification.Builder n1 = new Notification.Builder(mContext, "test");
- Notification.Builder n2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigTextStyle());
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testStyleChangeVisiblyDifferent_styleToNoStyle() {
- Notification.Builder n2 = new Notification.Builder(mContext, "test");
- Notification.Builder n1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigTextStyle());
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testStyleChangeVisiblyDifferent_changeStyle() {
- Notification.Builder n1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.InboxStyle());
- Notification.Builder n2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigTextStyle());
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testInboxTextChange() {
- Notification.Builder nInbox1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.InboxStyle().addLine("a").addLine("b"));
- Notification.Builder nInbox2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.InboxStyle().addLine("b").addLine("c"));
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nInbox1, nInbox2));
- }
-
- @Test
- public void testBigTextTextChange() {
- Notification.Builder nBigText1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigTextStyle().bigText("something"));
- Notification.Builder nBigText2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigTextStyle().bigText("else"));
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigText1, nBigText2));
- }
-
- @Test
- public void testBigPictureChange() {
- Bitmap bitA = mock(Bitmap.class);
- when(bitA.getGenerationId()).thenReturn(100);
- Bitmap bitB = mock(Bitmap.class);
- when(bitB.getGenerationId()).thenReturn(200);
-
- Notification.Builder nBigPic1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigPictureStyle().bigPicture(bitA));
- Notification.Builder nBigPic2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.BigPictureStyle().bigPicture(bitB));
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2));
- }
-
- @Test
- public void testMessagingChange_text() {
- Notification.Builder nM1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message(
- "a", 100, mock(Person.class))));
- Notification.Builder nM2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message(
- "a", 100, mock(Person.class)))
- .addMessage(new Notification.MessagingStyle.Message(
- "b", 100, mock(Person.class)))
- );
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
- }
-
- @Test
- public void testMessagingChange_data() {
- Notification.Builder nM1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message(
- "a", 100, mock(Person.class))
- .setData("text", mock(Uri.class))));
- Notification.Builder nM2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message(
- "a", 100, mock(Person.class))));
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
- }
-
- @Test
- public void testMessagingChange_sender() {
- Person a = mock(Person.class);
- when(a.getName()).thenReturn("A");
- Person b = mock(Person.class);
- when(b.getName()).thenReturn("b");
- Notification.Builder nM1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
- Notification.Builder nM2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
- }
-
- @Test
- public void testMessagingChange_key() {
- Person a = mock(Person.class);
- when(a.getKey()).thenReturn("A");
- Person b = mock(Person.class);
- when(b.getKey()).thenReturn("b");
- Notification.Builder nM1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
- Notification.Builder nM2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
-
- assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
- }
-
- @Test
- public void testMessagingChange_ignoreTimeChange() {
- Notification.Builder nM1 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message(
- "a", 100, mock(Person.class))));
- Notification.Builder nM2 = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle("")
- .addMessage(new Notification.MessagingStyle.Message(
- "a", 1000, mock(Person.class)))
- );
-
- assertFalse(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
- }
-
- @Test
- public void testRemoteViews_nullChange() {
- Notification.Builder n1 = new Notification.Builder(mContext, "test")
- .setContent(mock(RemoteViews.class));
- Notification.Builder n2 = new Notification.Builder(mContext, "test");
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test");
- n2 = new Notification.Builder(mContext, "test")
- .setContent(mock(RemoteViews.class));
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test")
- .setCustomBigContentView(mock(RemoteViews.class));
- n2 = new Notification.Builder(mContext, "test");
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test");
- n2 = new Notification.Builder(mContext, "test")
- .setCustomBigContentView(mock(RemoteViews.class));
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test");
- n2 = new Notification.Builder(mContext, "test");
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
- }
-
- @Test
- public void testRemoteViews_layoutChange() {
- RemoteViews a = mock(RemoteViews.class);
- when(a.getLayoutId()).thenReturn(234);
- RemoteViews b = mock(RemoteViews.class);
- when(b.getLayoutId()).thenReturn(189);
-
- Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
- Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
- }
-
- @Test
- public void testRemoteViews_layoutSame() {
- RemoteViews a = mock(RemoteViews.class);
- when(a.getLayoutId()).thenReturn(234);
- RemoteViews b = mock(RemoteViews.class);
- when(b.getLayoutId()).thenReturn(234);
-
- Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
- Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
- }
-
- @Test
- public void testRemoteViews_sequenceChange() {
- RemoteViews a = mock(RemoteViews.class);
- when(a.getLayoutId()).thenReturn(234);
- when(a.getSequenceNumber()).thenReturn(1);
- RemoteViews b = mock(RemoteViews.class);
- when(b.getLayoutId()).thenReturn(234);
- when(b.getSequenceNumber()).thenReturn(2);
-
- Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
- Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
- assertTrue(Notification.areRemoteViewsChanged(n1, n2));
- }
-
- @Test
- public void testRemoteViews_sequenceSame() {
- RemoteViews a = mock(RemoteViews.class);
- when(a.getLayoutId()).thenReturn(234);
- when(a.getSequenceNumber()).thenReturn(1);
- RemoteViews b = mock(RemoteViews.class);
- when(b.getLayoutId()).thenReturn(234);
- when(b.getSequenceNumber()).thenReturn(1);
-
- Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
- Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
-
- n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
- n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
- assertFalse(Notification.areRemoteViewsChanged(n1, n2));
- }
-
- @Test
- public void testActionsDifferent_null() {
- Notification n1 = new Notification.Builder(mContext, "test")
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .build();
-
- assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testActionsDifferentSame() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- Notification n1 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
- .build();
-
- assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testActionsDifferentText() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- Notification n1 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
- .build();
-
- assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testActionsDifferentSpannables() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- Notification n1 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon,
- new SpannableStringBuilder().append("test1",
- new StyleSpan(Typeface.BOLD),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE),
- intent).build())
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "test1", intent).build())
- .build();
-
- assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testActionsDifferentNumber() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- Notification n1 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
- .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
- .build();
-
- assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testActionsDifferentIntent() {
- PendingIntent intent1 = mock(PendingIntent.class);
- PendingIntent intent2 = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- Notification n1 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent1).build())
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent2).build())
- .build();
-
- assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testActionsIgnoresRemoteInputs() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- Notification n1 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
- .addRemoteInput(new RemoteInput.Builder("a")
- .setChoices(new CharSequence[] {"i", "m"})
- .build())
- .build())
- .build();
- Notification n2 = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
- .addRemoteInput(new RemoteInput.Builder("a")
- .setChoices(new CharSequence[] {"t", "m"})
- .build())
- .build())
- .build();
-
- assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
- }
-
- @Test
- public void testFreeformRemoteInputActionPair_noRemoteInput() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
- Notification notification = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
- .build())
- .build();
- assertNull(notification.findRemoteInputActionPair(false));
- }
-
- @Test
- public void testFreeformRemoteInputActionPair_hasRemoteInput() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- RemoteInput remoteInput = new RemoteInput.Builder("a").build();
-
- Notification.Action actionWithRemoteInput =
- new Notification.Action.Builder(icon, "TEXT 1", intent)
- .addRemoteInput(remoteInput)
- .addRemoteInput(remoteInput)
- .build();
-
- Notification.Action actionWithoutRemoteInput =
- new Notification.Action.Builder(icon, "TEXT 2", intent)
- .build();
-
- Notification notification = new Notification.Builder(mContext, "test")
- .addAction(actionWithoutRemoteInput)
- .addAction(actionWithRemoteInput)
- .build();
-
- Pair<RemoteInput, Notification.Action> remoteInputActionPair =
- notification.findRemoteInputActionPair(false);
-
- assertNotNull(remoteInputActionPair);
- assertEquals(remoteInput, remoteInputActionPair.first);
- assertEquals(actionWithRemoteInput, remoteInputActionPair.second);
- }
-
- @Test
- public void testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
- Notification notification = new Notification.Builder(mContext, "test")
- .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
- .addRemoteInput(
- new RemoteInput.Builder("a")
- .setAllowFreeFormInput(false).build())
- .build())
- .build();
- assertNull(notification.findRemoteInputActionPair(true));
- }
-
- @Test
- public void testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput() {
- PendingIntent intent = mock(PendingIntent.class);
- Icon icon = mock(Icon.class);
-
- RemoteInput remoteInput =
- new RemoteInput.Builder("a").setAllowFreeFormInput(false).build();
- RemoteInput freeformRemoteInput =
- new RemoteInput.Builder("b").setAllowFreeFormInput(true).build();
-
- Notification.Action actionWithFreeformRemoteInput =
- new Notification.Action.Builder(icon, "TEXT 1", intent)
- .addRemoteInput(remoteInput)
- .addRemoteInput(freeformRemoteInput)
- .build();
-
- Notification.Action actionWithoutFreeformRemoteInput =
- new Notification.Action.Builder(icon, "TEXT 2", intent)
- .addRemoteInput(remoteInput)
- .build();
-
- Notification notification = new Notification.Builder(mContext, "test")
- .addAction(actionWithoutFreeformRemoteInput)
- .addAction(actionWithFreeformRemoteInput)
- .build();
-
- Pair<RemoteInput, Notification.Action> remoteInputActionPair =
- notification.findRemoteInputActionPair(true);
-
- assertNotNull(remoteInputActionPair);
- assertEquals(freeformRemoteInput, remoteInputActionPair.first);
- assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second);
- }
-}
-
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index d5e336b1cf2f..eed32d7d815c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -40,14 +40,18 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
+import android.app.ActivityOptions;
import android.app.WaitResult;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.os.Binder;
import android.os.ConditionVariable;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
@@ -308,4 +312,40 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
waitHandlerIdle(mAtm.mH);
verify(mRootWindowContainer, timeout(TIMEOUT_MS)).startHomeOnEmptyDisplays("userUnlocked");
}
+
+ /** Verifies that launch from recents sets the launch cookie on the activity. */
+ @Test
+ public void testStartActivityFromRecents_withLaunchCookie() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+
+ IBinder launchCookie = new Binder("test_launch_cookie");
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchCookie(launchCookie);
+ SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(options.toBundle());
+
+ doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
+ anyInt(), any());
+
+ mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions);
+
+ assertThat(activity.mLaunchCookie).isEqualTo(launchCookie);
+ verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
+ }
+
+ /** Verifies that launch from recents doesn't set the launch cookie on the activity. */
+ @Test
+ public void testStartActivityFromRecents_withoutLaunchCookie() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+
+ SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(
+ ActivityOptions.makeBasic().toBundle());
+
+ doNothing().when(mSupervisor.mService).moveTaskToFrontLocked(eq(null), eq(null), anyInt(),
+ anyInt(), any());
+
+ mSupervisor.startActivityFromRecents(-1, -1, activity.getRootTaskId(), safeOptions);
+
+ assertThat(activity.mLaunchCookie).isNull();
+ verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 1cd0b198ff5a..e30e5dbcaf46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -242,7 +242,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
private IOnBackInvokedCallback createOnBackInvokedCallback() {
return new IOnBackInvokedCallback.Stub() {
@Override
- public void onBackStarted() {
+ public void onBackStarted(BackEvent backEvent) {
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
index e57ad5d9ff8c..24e932f36f80 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
@@ -57,10 +57,20 @@ public class SafeActivityOptionsTest {
.setLaunchTaskDisplayArea(token)
.setLaunchDisplayId(launchDisplayId)
.setCallerDisplayId(callerDisplayId))
- .selectiveCloneDisplayOptions();
+ .selectiveCloneLaunchOptions();
assertSame(clone.getOriginalOptions().getLaunchTaskDisplayArea(), token);
assertEquals(clone.getOriginalOptions().getLaunchDisplayId(), launchDisplayId);
assertEquals(clone.getOriginalOptions().getCallerDisplayId(), callerDisplayId);
}
+
+ @Test
+ public void test_selectiveCloneLunchRootTask() {
+ final WindowContainerToken token = mock(WindowContainerToken.class);
+ final SafeActivityOptions clone = new SafeActivityOptions(ActivityOptions.makeBasic()
+ .setLaunchRootTask(token))
+ .selectiveCloneLaunchOptions();
+
+ assertSame(clone.getOriginalOptions().getLaunchRootTask(), token);
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 3da47110fb49..352d8d115b6a 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -136,9 +136,6 @@ public class VoiceInteractionManagerService extends SystemService {
private final RemoteCallbackList<IVoiceInteractionSessionListener>
mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
- // TODO(b/226201975): remove once RoleService supports pre-created users
- private final ArrayList<UserHandle> mIgnoredPreCreatedUsers = new ArrayList<>();
-
public VoiceInteractionManagerService(Context context) {
super(context);
mContext = context;
@@ -308,24 +305,14 @@ public class VoiceInteractionManagerService extends SystemService {
return hotwordDetectionConnection.mIdentity;
}
+ // TODO(b/226201975): remove this method once RoleService supports pre-created users
@Override
public void onPreCreatedUserConversion(int userId) {
- Slogf.d(TAG, "onPreCreatedUserConversion(%d)", userId);
-
- for (int i = 0; i < mIgnoredPreCreatedUsers.size(); i++) {
- UserHandle preCreatedUser = mIgnoredPreCreatedUsers.get(i);
- if (preCreatedUser.getIdentifier() == userId) {
- Slogf.d(TAG, "Updating role on pre-created user %d", userId);
- mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
- preCreatedUser);
- mIgnoredPreCreatedUsers.remove(i);
- return;
- }
- }
- Slogf.w(TAG, "onPreCreatedUserConversion(%d): not available on "
- + "mIgnoredPreCreatedUserIds (%s)", userId, mIgnoredPreCreatedUsers);
+ Slogf.d(TAG, "onPreCreatedUserConversion(%d): calling onRoleHoldersChanged() again",
+ userId);
+ mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
+ UserHandle.of(userId));
}
-
}
// implementation entry point and binder service
@@ -807,8 +794,10 @@ public class VoiceInteractionManagerService extends SystemService {
if (TextUtils.isEmpty(curInteractor)) {
return null;
}
- if (DEBUG) Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
+ if (DEBUG) {
+ Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
+ " user=" + userHandle);
+ }
return ComponentName.unflattenFromString(curInteractor);
}
@@ -816,8 +805,9 @@ public class VoiceInteractionManagerService extends SystemService {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.VOICE_INTERACTION_SERVICE,
comp != null ? comp.flattenToShortString() : "", userHandle);
- if (DEBUG) Slog.d(TAG, "setCurInteractor comp=" + comp
- + " user=" + userHandle);
+ if (DEBUG) {
+ Slog.d(TAG, "setCurInteractor comp=" + comp + " user=" + userHandle);
+ }
}
ComponentName findAvailRecognizer(String prefPackage, int userHandle) {
@@ -1912,7 +1902,6 @@ public class VoiceInteractionManagerService extends SystemService {
pw.println(" mTemporarilyDisabled: " + mTemporarilyDisabled);
pw.println(" mCurUser: " + mCurUser);
pw.println(" mCurUserSupported: " + mCurUserSupported);
- pw.println(" mIgnoredPreCreatedUsers: " + mIgnoredPreCreatedUsers);
dumpSupportedUsers(pw, " ");
mDbHelper.dump(pw);
if (mImpl == null) {
@@ -2026,6 +2015,11 @@ public class VoiceInteractionManagerService extends SystemService {
List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user);
+ if (DEBUG) {
+ Slogf.d(TAG, "onRoleHoldersChanged(%s, %s): roleHolders=%s", roleName, user,
+ roleHolders);
+ }
+
// TODO(b/226201975): this method is beling called when a pre-created user is added,
// at which point it doesn't have any role holders. But it's not called again when
// the actual user is added (i.e., when the pre-created user is converted), so we
@@ -2036,9 +2030,9 @@ public class VoiceInteractionManagerService extends SystemService {
if (roleHolders.isEmpty()) {
UserInfo userInfo = mUserManagerInternal.getUserInfo(user.getIdentifier());
if (userInfo != null && userInfo.preCreated) {
- Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now",
- userInfo.toFullString());
- mIgnoredPreCreatedUsers.add(user);
+ Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now,"
+ + " this method will be called again when it's converted to a real"
+ + " user", userInfo.toFullString());
return;
}
}
diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt
index b2c5f4ac767b..8e62388c860f 100644
--- a/tools/xmlpersistence/src/main/kotlin/Generator.kt
+++ b/tools/xmlpersistence/src/main/kotlin/Generator.kt
@@ -149,6 +149,7 @@ private val ClassFieldInfo.allClassFields: List<ClassFieldInfo>
when (field) {
is ClassFieldInfo -> this += field.allClassFields
is ListFieldInfo -> this += field.element.allClassFields
+ else -> {}
}
}
}